home: add tab customization
Finally add tab customization. This implementation is a bit ugly, but I had to futureproof it for playlists and I'm planning to clean up a lot of the duplicate code across the app. This addition notably allows a default tab to be set, which is something that was widely requested in #12. This UI rework finally allows it to be added.
This commit is contained in:
parent
a253cfccc4
commit
23d1be8ebc
39 changed files with 687 additions and 203 deletions
|
@ -46,14 +46,13 @@ I primarily built Auxio for myself, but you can use it too, I guess.
|
||||||
- Search Functionality
|
- Search Functionality
|
||||||
- Audio Focus / Headset Management
|
- Audio Focus / Headset Management
|
||||||
- No internet connectivity whatsoever
|
- No internet connectivity whatsoever
|
||||||
- Kotlin from the ground-up
|
- No rounded album corners
|
||||||
- Modular, feature-based architecture
|
|
||||||
- No rounded corners
|
|
||||||
|
|
||||||
## To possibly come in the future:
|
## To possibly come in the future:
|
||||||
|
|
||||||
- Playlists
|
- Playlists
|
||||||
- Liked songs
|
- Liked songs
|
||||||
|
- Improved tablet layouts
|
||||||
- More notification actions
|
- More notification actions
|
||||||
- Other things, possibly
|
- Other things, possibly
|
||||||
|
|
||||||
|
|
|
@ -97,11 +97,13 @@ dependencies {
|
||||||
implementation "com.google.android.exoplayer:exoplayer-core:2.15.1"
|
implementation "com.google.android.exoplayer:exoplayer-core:2.15.1"
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
implementation 'io.coil-kt:coil:1.3.2'
|
implementation 'io.coil-kt:coil:1.4.0'
|
||||||
|
|
||||||
// Material
|
// Material
|
||||||
implementation "com.google.android.material:material:1.5.0-alpha04"
|
implementation "com.google.android.material:material:1.5.0-alpha04"
|
||||||
|
|
||||||
|
// Fast scrolling
|
||||||
|
// TODO: Merge eventually
|
||||||
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
||||||
|
|
||||||
// --- DEBUG ---
|
// --- DEBUG ---
|
||||||
|
|
|
@ -49,7 +49,6 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.SortMode
|
import org.oxycblt.auxio.ui.SortMode
|
||||||
import org.oxycblt.auxio.ui.memberBinding
|
|
||||||
import org.oxycblt.auxio.util.applyEdge
|
import org.oxycblt.auxio.util.applyEdge
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
|
@ -57,16 +56,12 @@ import org.oxycblt.auxio.util.logE
|
||||||
/**
|
/**
|
||||||
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
||||||
* views for each respective fragment.
|
* views for each respective fragment.
|
||||||
* FIXME: More UI glitches:
|
* FIXME: Edge-to-edge is borked still, unsure how to really fix this aside from making some
|
||||||
* - AppBar will just...expand. For no reason. If you navigate away while it's partially
|
|
||||||
* collapsed. No, I don't know why. Guess I have to save the state myself.
|
|
||||||
* - Edge-to-edge is borked still, unsure how to really fix this aside from making some
|
|
||||||
* magic layout like Material Files, but even then it might not work since the scrolling
|
* magic layout like Material Files, but even then it might not work since the scrolling
|
||||||
* views are not laid side-by-side to the layout itself.
|
* views are not laid side-by-side to the layout itself.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
private val binding: FragmentHomeBinding by memberBinding(FragmentHomeBinding::inflate)
|
|
||||||
private val detailModel: DetailViewModel by activityViewModels()
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
private val homeModel: HomeViewModel by activityViewModels()
|
private val homeModel: HomeViewModel by activityViewModels()
|
||||||
|
|
||||||
|
@ -75,6 +70,7 @@ class HomeFragment : Fragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
|
val binding = FragmentHomeBinding.inflate(inflater)
|
||||||
val sortItem: MenuItem
|
val sortItem: MenuItem
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
@ -86,6 +82,12 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.homeAppbar.apply {
|
binding.homeAppbar.apply {
|
||||||
|
// I have no idea how to clip the collapsing toolbar while still making the elevation
|
||||||
|
// overlay bleed into the status bar, so I take the easy way out and just fade the
|
||||||
|
// toolbar when the offset changes.
|
||||||
|
// Note: Don't merge this with the other OnOffsetChangedListener, as this one needs
|
||||||
|
// to be added pre-start to work correctly while the other one needs to be posted to
|
||||||
|
// work correctly
|
||||||
addOnOffsetChangedListener(
|
addOnOffsetChangedListener(
|
||||||
AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
|
AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
|
||||||
binding.homeToolbar.alpha = (binding.homeToolbar.height + verticalOffset) /
|
binding.homeToolbar.alpha = (binding.homeToolbar.height + verticalOffset) /
|
||||||
|
@ -93,8 +95,13 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// One issue that comes with using our fast scroller is that it allows scrolling
|
||||||
|
// without the AppBar actually being collapsed in the process. This results in
|
||||||
|
// the RecyclerView being clipped if you scroll down far enough. To fix this, we
|
||||||
|
// add another OnOffsetChangeListener that adds padding to the RecyclerView whenever
|
||||||
|
// the Toolbar is collapsed. This is not really ideal, as it forces a relayout and
|
||||||
|
// some edge-effect glitches whenever we scroll, but its the best we can do.
|
||||||
post {
|
post {
|
||||||
// To add our fast scroller, we need to
|
|
||||||
val vOffset = (
|
val vOffset = (
|
||||||
(layoutParams as CoordinatorLayout.LayoutParams)
|
(layoutParams as CoordinatorLayout.LayoutParams)
|
||||||
.behavior as AppBarLayout.Behavior
|
.behavior as AppBarLayout.Behavior
|
||||||
|
@ -135,7 +142,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
R.id.submenu_sorting -> { }
|
R.id.submenu_sorting -> { }
|
||||||
|
|
||||||
// Sorting option was selected, check then and update the mode
|
// Sorting option was selected, mark it as selected and update the mode
|
||||||
else -> {
|
else -> {
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
|
|
||||||
|
@ -179,7 +186,7 @@ class HomeFragment : Fragment() {
|
||||||
// We know that there will only be a fixed amount of tabs, so we manually set this
|
// We know that there will only be a fixed amount of tabs, so we manually set this
|
||||||
// limit to that. This also prevents the appbar lift state from being confused during
|
// limit to that. This also prevents the appbar lift state from being confused during
|
||||||
// page transitions.
|
// page transitions.
|
||||||
offscreenPageLimit = homeModel.tabs.value!!.size
|
offscreenPageLimit = homeModel.tabs.size
|
||||||
|
|
||||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) = homeModel.updateCurrentTab(position)
|
override fun onPageSelected(position: Int) = homeModel.updateCurrentTab(position)
|
||||||
|
@ -187,7 +194,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
TabLayoutMediator(binding.homeTabs, binding.homePager) { tab, pos ->
|
TabLayoutMediator(binding.homeTabs, binding.homePager) { tab, pos ->
|
||||||
val labelRes = when (homeModel.tabs.value!![pos]) {
|
val labelRes = when (homeModel.tabs[pos]) {
|
||||||
DisplayMode.SHOW_SONGS -> R.string.lbl_songs
|
DisplayMode.SHOW_SONGS -> R.string.lbl_songs
|
||||||
DisplayMode.SHOW_ALBUMS -> R.string.lbl_albums
|
DisplayMode.SHOW_ALBUMS -> R.string.lbl_albums
|
||||||
DisplayMode.SHOW_ARTISTS -> R.string.lbl_artists
|
DisplayMode.SHOW_ARTISTS -> R.string.lbl_artists
|
||||||
|
@ -199,7 +206,19 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
|
homeModel.recreateTabs.observe(viewLifecycleOwner) { recreate ->
|
||||||
|
// notifyDataSetChanged is not practical for recreating here since it will cache
|
||||||
|
// the previous fragments. Just instantiate a whole new adapter.
|
||||||
|
if (recreate) {
|
||||||
|
binding.homePager.currentItem = 0
|
||||||
|
binding.homePager.adapter = HomePagerAdapter()
|
||||||
|
homeModel.finishRecreateTabs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
homeModel.curTab.observe(viewLifecycleOwner) { tab ->
|
homeModel.curTab.observe(viewLifecycleOwner) { tab ->
|
||||||
|
// Make sure that we update the scrolling view and allowed menu items before whenever
|
||||||
|
// the tab changes.
|
||||||
binding.homeAppbar.liftOnScrollTargetViewId = when (requireNotNull(tab)) {
|
binding.homeAppbar.liftOnScrollTargetViewId = when (requireNotNull(tab)) {
|
||||||
DisplayMode.SHOW_SONGS -> {
|
DisplayMode.SHOW_SONGS -> {
|
||||||
updateSortMenu(sortItem, tab)
|
updateSortMenu(sortItem, tab)
|
||||||
|
@ -280,10 +299,10 @@ class HomeFragment : Fragment() {
|
||||||
private inner class HomePagerAdapter :
|
private inner class HomePagerAdapter :
|
||||||
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
|
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
|
||||||
|
|
||||||
override fun getItemCount(): Int = homeModel.tabs.value!!.size
|
override fun getItemCount(): Int = homeModel.tabs.size
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
return when (homeModel.tabs.value!![position]) {
|
return when (homeModel.tabs[position]) {
|
||||||
DisplayMode.SHOW_SONGS -> SongListFragment()
|
DisplayMode.SHOW_SONGS -> SongListFragment()
|
||||||
DisplayMode.SHOW_ALBUMS -> AlbumListFragment()
|
DisplayMode.SHOW_ALBUMS -> AlbumListFragment()
|
||||||
DisplayMode.SHOW_ARTISTS -> ArtistListFragment()
|
DisplayMode.SHOW_ARTISTS -> ArtistListFragment()
|
||||||
|
|
|
@ -27,14 +27,18 @@ 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.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
|
import org.oxycblt.auxio.settings.tabs.Tab
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.SortMode
|
import org.oxycblt.auxio.ui.SortMode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel for managing [HomeFragment]'s data and sorting modes.
|
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
|
||||||
* TODO: Custom tabs
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
|
private val musicStore = MusicStore.getInstance()
|
||||||
|
private val settingsManager = SettingsManager.getInstance()
|
||||||
|
|
||||||
private val mSongs = MutableLiveData(listOf<Song>())
|
private val mSongs = MutableLiveData(listOf<Song>())
|
||||||
val songs: LiveData<List<Song>> get() = mSongs
|
val songs: LiveData<List<Song>> get() = mSongs
|
||||||
|
|
||||||
|
@ -47,32 +51,38 @@ class HomeViewModel : ViewModel() {
|
||||||
private val mGenres = MutableLiveData(listOf<Genre>())
|
private val mGenres = MutableLiveData(listOf<Genre>())
|
||||||
val genres: LiveData<List<Genre>> get() = mGenres
|
val genres: LiveData<List<Genre>> get() = mGenres
|
||||||
|
|
||||||
private val mTabs = MutableLiveData(
|
var tabs: List<DisplayMode> = settingsManager.visibleTabs
|
||||||
arrayOf(
|
private set
|
||||||
DisplayMode.SHOW_SONGS, DisplayMode.SHOW_ALBUMS,
|
|
||||||
DisplayMode.SHOW_ARTISTS, DisplayMode.SHOW_GENRES
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val tabs: LiveData<Array<DisplayMode>> = mTabs
|
|
||||||
|
|
||||||
private val mCurTab = MutableLiveData(mTabs.value!![0])
|
private val mCurTab = MutableLiveData(tabs[0])
|
||||||
val curTab: LiveData<DisplayMode> = mCurTab
|
val curTab: LiveData<DisplayMode> = mCurTab
|
||||||
|
|
||||||
private val musicStore = MusicStore.getInstance()
|
/**
|
||||||
private val settingsManager = SettingsManager.getInstance()
|
* Marker to recreate all library tabs, usually initiated by a settings change.
|
||||||
|
* When this flag is set, all tabs (and their respective viewpager fragments) will be
|
||||||
|
* recreated from scratch.
|
||||||
|
*/
|
||||||
|
private val mRecreateTabs = MutableLiveData(false)
|
||||||
|
val recreateTabs: LiveData<Boolean> = mRecreateTabs
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs)
|
mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs)
|
||||||
mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums)
|
mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums)
|
||||||
mArtists.value = settingsManager.libArtistSort.sortModels(musicStore.artists)
|
mArtists.value = settingsManager.libArtistSort.sortModels(musicStore.artists)
|
||||||
mGenres.value = settingsManager.libGenreSort.sortModels(musicStore.genres)
|
mGenres.value = settingsManager.libGenreSort.sortModels(musicStore.genres)
|
||||||
|
|
||||||
|
settingsManager.addCallback(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the current tab based off of the new ViewPager position.
|
* Update the current tab based off of the new ViewPager position.
|
||||||
*/
|
*/
|
||||||
fun updateCurrentTab(pos: Int) {
|
fun updateCurrentTab(pos: Int) {
|
||||||
mCurTab.value = mTabs.value!![pos]
|
mCurTab.value = tabs[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finishRecreateTabs() {
|
||||||
|
mRecreateTabs.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSortForDisplay(displayMode: DisplayMode): SortMode {
|
fun getSortForDisplay(displayMode: DisplayMode): SortMode {
|
||||||
|
@ -108,4 +118,16 @@ class HomeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- OVERRIDES ---
|
||||||
|
|
||||||
|
override fun onLibTabsUpdate(libTabs: Array<Tab>) {
|
||||||
|
tabs = settingsManager.visibleTabs
|
||||||
|
mRecreateTabs.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
settingsManager.removeCallback(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,10 @@ import org.oxycblt.auxio.ui.memberBinding
|
||||||
import org.oxycblt.auxio.util.applySpans
|
import org.oxycblt.auxio.util.applySpans
|
||||||
import org.oxycblt.auxio.util.resolveDrawable
|
import org.oxycblt.auxio.util.resolveDrawable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Base [Fragment] implementing the base features shared across all detail fragments.
|
||||||
|
*
|
||||||
|
*/
|
||||||
abstract class HomeListFragment : Fragment() {
|
abstract class HomeListFragment : Fragment() {
|
||||||
protected val binding: FragmentHomeListBinding by memberBinding(
|
protected val binding: FragmentHomeListBinding by memberBinding(
|
||||||
FragmentHomeListBinding::inflate
|
FragmentHomeListBinding::inflate
|
||||||
|
@ -83,6 +87,9 @@ abstract class HomeListFragment : Fragment() {
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun updateData(newData: List<T>) {
|
fun updateData(newData: List<T>) {
|
||||||
data = newData
|
data = newData
|
||||||
|
|
||||||
|
// notifyDataSetChanged here is okay, as we have no idea how the layout changed when
|
||||||
|
// we re-sort and ListAdapter causes the scroll position to get messed up
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ import org.oxycblt.auxio.util.logD
|
||||||
*
|
*
|
||||||
* I'm pretty sure nothing is going to happen and MediaStore will continue to be neglected and
|
* I'm pretty sure nothing is going to happen and MediaStore will continue to be neglected and
|
||||||
* probably deprecated eventually for a "new" API that just coincidentally excludes music indexing.
|
* probably deprecated eventually for a "new" API that just coincidentally excludes music indexing.
|
||||||
* Because go **** yourself for wanting to listen to music you own. Be a good consoomer and listen
|
* Because go screw yourself for wanting to listen to music you own. Be a good consoomer and listen
|
||||||
* to your AlgoPop StreamMix™ instead.
|
* to your AlgoPop StreamMix™ instead.
|
||||||
*
|
*
|
||||||
* I wish I was born in the neolithic.
|
* I wish I was born in the neolithic.
|
||||||
|
|
|
@ -133,8 +133,6 @@ class SearchFragment : Fragment() {
|
||||||
searchModel.searchResults.observe(viewLifecycleOwner) { results ->
|
searchModel.searchResults.observe(viewLifecycleOwner) { results ->
|
||||||
searchAdapter.submitList(results) {
|
searchAdapter.submitList(results) {
|
||||||
// We've just scrolled back to the top, reset the lifted state
|
// We've just scrolled back to the top, reset the lifted state
|
||||||
// TODO: Maybe find a better way to keep scroll state when the search
|
|
||||||
// results didn't actually change.
|
|
||||||
binding.searchRecycler.scrollToPosition(0)
|
binding.searchRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,9 @@ import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.accent.AccentDialog
|
import org.oxycblt.auxio.accent.AccentDialog
|
||||||
import org.oxycblt.auxio.excluded.ExcludedDialog
|
import org.oxycblt.auxio.excluded.ExcludedDialog
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
import org.oxycblt.auxio.settings.pref.IntListPrefDialog
|
||||||
|
import org.oxycblt.auxio.settings.pref.IntListPreference
|
||||||
|
import org.oxycblt.auxio.settings.tabs.TabCustomizeDialog
|
||||||
import org.oxycblt.auxio.util.applyEdge
|
import org.oxycblt.auxio.util.applyEdge
|
||||||
import org.oxycblt.auxio.util.isNight
|
import org.oxycblt.auxio.util.isNight
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
@ -130,6 +133,13 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
summary = Accent.get().getDetailedSummary(context)
|
summary = Accent.get().getDetailedSummary(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsManager.KEY_LIB_TABS -> {
|
||||||
|
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS -> {
|
SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS -> {
|
||||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
|
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
|
||||||
Coil.imageLoader(requireContext()).apply {
|
Coil.imageLoader(requireContext()).apply {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import org.oxycblt.auxio.accent.Accent
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
|
import org.oxycblt.auxio.settings.tabs.Tab
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.SortMode
|
import org.oxycblt.auxio.ui.SortMode
|
||||||
|
|
||||||
|
@ -70,9 +71,21 @@ class SettingsManager private constructor(context: Context) :
|
||||||
val useAltNotifAction: Boolean
|
val useAltNotifAction: Boolean
|
||||||
get() = sharedPrefs.getBoolean(KEY_USE_ALT_NOTIFICATION_ACTION, false)
|
get() = sharedPrefs.getBoolean(KEY_USE_ALT_NOTIFICATION_ACTION, false)
|
||||||
|
|
||||||
/**
|
/** The current library tabs preferred by the user. */
|
||||||
* Whether to even loading embedded covers
|
var libTabs: Array<Tab>
|
||||||
*/
|
get() = Tab.fromSequence(sharedPrefs.getInt(KEY_LIB_TABS, Tab.SEQUENCE_DEFAULT))
|
||||||
|
?: Tab.fromSequence(Tab.SEQUENCE_DEFAULT)!!
|
||||||
|
set(value) {
|
||||||
|
sharedPrefs.edit {
|
||||||
|
putInt(KEY_LIB_TABS, Tab.toSequence(value))
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The currently visible library tabs */
|
||||||
|
val visibleTabs: List<DisplayMode> get() = libTabs.filterIsInstance<Tab.Visible>().map { it.mode }
|
||||||
|
|
||||||
|
/** Whether to load embedded covers */
|
||||||
val showCovers: Boolean
|
val showCovers: Boolean
|
||||||
get() = sharedPrefs.getBoolean(KEY_SHOW_COVERS, true)
|
get() = sharedPrefs.getBoolean(KEY_SHOW_COVERS, true)
|
||||||
|
|
||||||
|
@ -114,6 +127,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The song sort mode on HomeFragment **/
|
||||||
var libSongSort: SortMode
|
var libSongSort: SortMode
|
||||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE))
|
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE))
|
||||||
?: SortMode.ASCENDING
|
?: SortMode.ASCENDING
|
||||||
|
@ -124,6 +138,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The album sort mode on HomeFragment **/
|
||||||
var libAlbumSort: SortMode
|
var libAlbumSort: SortMode
|
||||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE))
|
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE))
|
||||||
?: SortMode.ASCENDING
|
?: SortMode.ASCENDING
|
||||||
|
@ -134,6 +149,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The artist sort mode on HomeFragment **/
|
||||||
var libArtistSort: SortMode
|
var libArtistSort: SortMode
|
||||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE))
|
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE))
|
||||||
?: SortMode.ASCENDING
|
?: SortMode.ASCENDING
|
||||||
|
@ -144,6 +160,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The genre sort mode on HomeFragment **/
|
||||||
var libGenreSort: SortMode
|
var libGenreSort: SortMode
|
||||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_GENRE_SORT, Int.MIN_VALUE))
|
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_GENRE_SORT, Int.MIN_VALUE))
|
||||||
?: SortMode.ASCENDING
|
?: SortMode.ASCENDING
|
||||||
|
@ -154,6 +171,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The detail album sort mode **/
|
||||||
var detailAlbumSort: SortMode
|
var detailAlbumSort: SortMode
|
||||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE))
|
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE))
|
||||||
?: SortMode.ASCENDING
|
?: SortMode.ASCENDING
|
||||||
|
@ -164,6 +182,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The detail artist sort mode **/
|
||||||
var detailArtistSort: SortMode
|
var detailArtistSort: SortMode
|
||||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE))
|
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE))
|
||||||
?: SortMode.YEAR
|
?: SortMode.YEAR
|
||||||
|
@ -174,6 +193,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The detail genre sort mode **/
|
||||||
var detailGenreSort: SortMode
|
var detailGenreSort: SortMode
|
||||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE))
|
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE))
|
||||||
?: SortMode.ASCENDING
|
?: SortMode.ASCENDING
|
||||||
|
@ -211,6 +231,10 @@ class SettingsManager private constructor(context: Context) :
|
||||||
KEY_QUALITY_COVERS -> callbacks.forEach {
|
KEY_QUALITY_COVERS -> callbacks.forEach {
|
||||||
it.onQualityCoverUpdate(useQualityCovers)
|
it.onQualityCoverUpdate(useQualityCovers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KEY_LIB_TABS -> callbacks.forEach {
|
||||||
|
it.onLibTabsUpdate(libTabs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +244,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
* context.
|
* context.
|
||||||
*/
|
*/
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
fun onLibTabsUpdate(libTabs: Array<Tab>) {}
|
||||||
fun onColorizeNotifUpdate(doColorize: Boolean) {}
|
fun onColorizeNotifUpdate(doColorize: Boolean) {}
|
||||||
fun onNotifActionUpdate(useAltAction: Boolean) {}
|
fun onNotifActionUpdate(useAltAction: Boolean) {}
|
||||||
fun onShowCoverUpdate(showCovers: Boolean) {}
|
fun onShowCoverUpdate(showCovers: Boolean) {}
|
||||||
|
@ -231,6 +256,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
const val KEY_BLACK_THEME = "KEY_BLACK_THEME"
|
const val KEY_BLACK_THEME = "KEY_BLACK_THEME"
|
||||||
const val KEY_ACCENT = "KEY_ACCENT2"
|
const val KEY_ACCENT = "KEY_ACCENT2"
|
||||||
|
|
||||||
|
const val KEY_LIB_TABS = "KEY_LIB_TABS"
|
||||||
const val KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
|
const val KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
|
||||||
const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"
|
const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"
|
||||||
const val KEY_USE_ALT_NOTIFICATION_ACTION = "KEY_ALT_NOTIF_ACTION"
|
const val KEY_USE_ALT_NOTIFICATION_ACTION = "KEY_ALT_NOTIF_ACTION"
|
||||||
|
|
|
@ -16,7 +16,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.settings
|
package org.oxycblt.auxio.settings.pref
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
@ -24,6 +24,9 @@ import androidx.preference.PreferenceFragmentCompat
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.ui.LifecycleDialog
|
import org.oxycblt.auxio.ui.LifecycleDialog
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dialog shown whenever an [IntListPreference] is shown.
|
||||||
|
*/
|
||||||
class IntListPrefDialog : LifecycleDialog() {
|
class IntListPrefDialog : LifecycleDialog() {
|
||||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||||
// Since we have to store the preference key as an argument, we have to find the
|
// Since we have to store the preference key as an argument, we have to find the
|
|
@ -16,7 +16,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.settings
|
package org.oxycblt.auxio.settings.pref
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
124
app/src/main/java/org/oxycblt/auxio/settings/tabs/Tab.kt
Normal file
124
app/src/main/java/org/oxycblt/auxio/settings/tabs/Tab.kt
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Auxio Project
|
||||||
|
* Tab.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* 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.settings.tabs
|
||||||
|
|
||||||
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
|
import org.oxycblt.auxio.util.logE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data representation of a library tab.
|
||||||
|
* A tab can come in two moves, [Visible] or [Invisible]. Invisibility means that the tab
|
||||||
|
* will still be present in the customization menu, but will not be shown on the home UI.
|
||||||
|
*
|
||||||
|
* Like other IO-bound datatypes in Auxio, tabs are stored in a binary format. However, tabs cannot
|
||||||
|
* be serialized on their own. Instead, they are saved as a sequence of tabs as shown below:
|
||||||
|
*
|
||||||
|
* 0bTAB1_TAB2_TAB3_TAB4_TAB5
|
||||||
|
*
|
||||||
|
* Where TABN is a chunk representing a tab at position N. TAB5 is reserved for playlists.
|
||||||
|
* Each chunk in a sequence is represented as:
|
||||||
|
*
|
||||||
|
* VTTT
|
||||||
|
*
|
||||||
|
* Where V is a bit representing the visibility and T is a 3-bit integer representing the
|
||||||
|
* [DisplayMode] ordinal for this tab.
|
||||||
|
*
|
||||||
|
* To serialize and deserialize a tab sequence, [toSequence] and [fromSequence] can be used
|
||||||
|
* respectively.
|
||||||
|
*
|
||||||
|
* By default, the tab order will be SONGS, ALBUMS, ARTISTS, GENRES, PLAYLISTS
|
||||||
|
*/
|
||||||
|
sealed class Tab(open val mode: DisplayMode) {
|
||||||
|
data class Visible(override val mode: DisplayMode) : Tab(mode)
|
||||||
|
data class Invisible(override val mode: DisplayMode) : Tab(mode)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The length a well-formed tab sequence should be **/
|
||||||
|
const val SEQUENCE_LEN = 4
|
||||||
|
/** The default tab sequence, represented in integer form **/
|
||||||
|
const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100
|
||||||
|
|
||||||
|
// Temporary value to make sure we create a 5-tab sequence even though playlists
|
||||||
|
// aren't implemented yet.
|
||||||
|
private const val TEMP_BIT_CAP = 20
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array [tabs] into a sequence of tabs.
|
||||||
|
*/
|
||||||
|
fun toSequence(tabs: Array<Tab>): Int {
|
||||||
|
// Like when deserializing, make sure there are no duplicate tabs for whatever reason.
|
||||||
|
val distinct = tabs.distinctBy { it.mode }
|
||||||
|
|
||||||
|
var sequence = 0b0100
|
||||||
|
var shift = TEMP_BIT_CAP
|
||||||
|
|
||||||
|
distinct.forEach { tab ->
|
||||||
|
val bin = when (tab) {
|
||||||
|
is Visible -> 1.shl(3) or tab.mode.ordinal
|
||||||
|
is Invisible -> tab.mode.ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence = sequence or bin.shl(shift)
|
||||||
|
shift -= 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a [sequence] into an array of tabs.
|
||||||
|
*/
|
||||||
|
fun fromSequence(sequence: Int): Array<Tab>? {
|
||||||
|
val tabs = mutableListOf<Tab>()
|
||||||
|
|
||||||
|
// Try to parse a mode for each chunk in the sequence.
|
||||||
|
// If we can't parse one, just skip it.
|
||||||
|
for (shift in (0..TEMP_BIT_CAP).reversed() step 4) {
|
||||||
|
val chunk = sequence.shr(shift) and 0b1111
|
||||||
|
|
||||||
|
val mode = when (chunk and 7) {
|
||||||
|
0 -> DisplayMode.SHOW_SONGS
|
||||||
|
1 -> DisplayMode.SHOW_ALBUMS
|
||||||
|
2 -> DisplayMode.SHOW_ARTISTS
|
||||||
|
3 -> DisplayMode.SHOW_GENRES
|
||||||
|
else -> continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out the visibility
|
||||||
|
tabs += if (chunk and 1.shl(3) != 0) {
|
||||||
|
Visible(mode)
|
||||||
|
} else {
|
||||||
|
Invisible(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there are no duplicate tabs
|
||||||
|
val distinct = tabs.distinctBy { it.mode }
|
||||||
|
|
||||||
|
// For safety, use the default configuration if something went wrong
|
||||||
|
// and we have an empty or larger-than-expected tab array.
|
||||||
|
if (distinct.isEmpty() || distinct.size < SEQUENCE_LEN) {
|
||||||
|
logE("Sequence size was ${distinct.size}, which is invalid. Using defaults instead")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return distinct.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Auxio Project
|
||||||
|
* TabAdapter.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* 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.settings.tabs
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.oxycblt.auxio.databinding.ItemTabBinding
|
||||||
|
import org.oxycblt.auxio.util.inflater
|
||||||
|
|
||||||
|
class TabAdapter(
|
||||||
|
private val touchHelper: ItemTouchHelper,
|
||||||
|
private val getTabs: () -> Array<Tab>,
|
||||||
|
private val onTabSwitch: (Tab) -> Unit,
|
||||||
|
) : RecyclerView.Adapter<TabAdapter.TabViewHolder>() {
|
||||||
|
private val tabs: Array<Tab> get() = getTabs()
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = Tab.SEQUENCE_LEN
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabViewHolder {
|
||||||
|
return TabViewHolder(ItemTabBinding.inflate(parent.context.inflater))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
|
||||||
|
holder.bind(tabs[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class TabViewHolder(
|
||||||
|
private val binding: ItemTabBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
init {
|
||||||
|
binding.root.layoutParams = RecyclerView.LayoutParams(
|
||||||
|
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
fun bind(tab: Tab) {
|
||||||
|
binding.root.apply {
|
||||||
|
setOnClickListener {
|
||||||
|
// Don't do a typical notifyDataSetChanged call here, because
|
||||||
|
// A. We don't have a real ViewModel state since this is a dialog
|
||||||
|
// B. Doing so would cause a relayout and the ripple effect to disappear
|
||||||
|
// Instead, simply notify a tab change and let TabCustomizeDialog handle it.
|
||||||
|
binding.tabIcon.isEnabled = !binding.tabIcon.isEnabled
|
||||||
|
binding.tabName.isEnabled = !binding.tabName.isEnabled
|
||||||
|
onTabSwitch(tab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.tabIcon.apply {
|
||||||
|
setImageResource(tab.mode.icon)
|
||||||
|
contentDescription = context.getString(tab.mode.string)
|
||||||
|
isEnabled = tab is Tab.Visible
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tabName.apply {
|
||||||
|
setText(tab.mode.string)
|
||||||
|
isEnabled = tab is Tab.Visible
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tabDragHandle.setOnTouchListener { _, motionEvent ->
|
||||||
|
binding.tabDragHandle.performClick()
|
||||||
|
|
||||||
|
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
|
touchHelper.startDrag(this)
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Auxio Project
|
||||||
|
* CustomizeListDialog.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* 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.settings.tabs
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import org.oxycblt.auxio.BuildConfig
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.databinding.DialogTabsBinding
|
||||||
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
|
import org.oxycblt.auxio.ui.LifecycleDialog
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dialog for customizing library tabs. This dialog does not rely on any specific ViewModel
|
||||||
|
* and serializes it's state instead of
|
||||||
|
* @author OxygenCobalt
|
||||||
|
*/
|
||||||
|
class TabCustomizeDialog : LifecycleDialog() {
|
||||||
|
private val settingsManager = SettingsManager.getInstance()
|
||||||
|
private var pendingTabs = settingsManager.libTabs
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
val binding = DialogTabsBinding.inflate(inflater)
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
// Restore any pending tab configurations
|
||||||
|
val tabs = Tab.fromSequence(savedInstanceState.getInt(KEY_TABS))
|
||||||
|
|
||||||
|
if (tabs != null) {
|
||||||
|
pendingTabs = tabs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up adapter & drag callback
|
||||||
|
val callback = TabDragCallback { pendingTabs }
|
||||||
|
val helper = ItemTouchHelper(callback)
|
||||||
|
val tabAdapter = TabAdapter(
|
||||||
|
helper,
|
||||||
|
getTabs = { pendingTabs },
|
||||||
|
onTabSwitch = { tab ->
|
||||||
|
// Don't find the specific tab [Which might be outdated due to the nature
|
||||||
|
// of how viewholders are bound], but instead simply look for the mode in
|
||||||
|
// the list of pending tabs and update that instead.
|
||||||
|
val index = pendingTabs.indexOfFirst { it.mode == tab.mode }
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
val curTab = pendingTabs[index]
|
||||||
|
|
||||||
|
pendingTabs[index] = when (curTab) {
|
||||||
|
is Tab.Visible -> Tab.Invisible(curTab.mode)
|
||||||
|
is Tab.Invisible -> Tab.Visible(curTab.mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||||
|
pendingTabs.filterIsInstance<Tab.Visible>().isNotEmpty()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
callback.addTabAdapter(tabAdapter)
|
||||||
|
|
||||||
|
binding.tabRecycler.apply {
|
||||||
|
adapter = tabAdapter
|
||||||
|
helper.attachToRecyclerView(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
|
outState.putInt(KEY_TABS, Tab.toSequence(pendingTabs))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||||
|
builder.setTitle(R.string.set_lib_tabs)
|
||||||
|
|
||||||
|
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
settingsManager.libTabs = pendingTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative button just dismisses, no need for a listener.
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = BuildConfig.APPLICATION_ID + ".tag.TAB_CUSTOMIZE"
|
||||||
|
const val KEY_TABS = BuildConfig.APPLICATION_ID + ".key.PENDING_TABS"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Auxio Project
|
||||||
|
* QueueDragCallback.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* 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.settings.tabs
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple [ItemTouchHelper.Callback] that handles dragging items in the tab customization menu.
|
||||||
|
* Unlike QueueAdapter's ItemTouchHelper, this one is bare and simple.
|
||||||
|
*/
|
||||||
|
class TabDragCallback(private val getTabs: () -> Array<Tab>) : ItemTouchHelper.Callback() {
|
||||||
|
private val tabs: Array<Tab> get() = getTabs()
|
||||||
|
private lateinit var tabAdapter: TabAdapter
|
||||||
|
|
||||||
|
override fun getMovementFlags(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder
|
||||||
|
): Int = makeFlag(
|
||||||
|
ItemTouchHelper.ACTION_STATE_DRAG,
|
||||||
|
ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onChildDraw(
|
||||||
|
c: Canvas,
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
dX: Float,
|
||||||
|
dY: Float,
|
||||||
|
actionState: Int,
|
||||||
|
isCurrentlyActive: Boolean
|
||||||
|
) {
|
||||||
|
// No fancy UI magic here. This is a dialog, we don't need to give it as much attention.
|
||||||
|
// Just make sure the built-in androidx code doesn't get in our way.
|
||||||
|
viewHolder.itemView.translationX = dX
|
||||||
|
viewHolder.itemView.translationY = dY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
viewHolder.itemView.translationX = 0f
|
||||||
|
viewHolder.itemView.translationY = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMove(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
tabs.swap(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
|
||||||
|
tabAdapter.notifyItemMoved(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the tab adapter to this callback.
|
||||||
|
* Done because there's a circular dependency between the two objects
|
||||||
|
*/
|
||||||
|
fun addTabAdapter(adapter: TabAdapter) {
|
||||||
|
tabAdapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T : Any> Array<T>.swap(from: Int, to: Int) {
|
||||||
|
val t = get(to)
|
||||||
|
val f = get(from)
|
||||||
|
|
||||||
|
set(from, t)
|
||||||
|
set(to, f)
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,15 +18,21 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui
|
package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enum for determining what items to show in a given list.
|
* 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.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
enum class DisplayMode {
|
enum class DisplayMode(@DrawableRes val icon: Int, @StringRes val string: Int) {
|
||||||
SHOW_GENRES,
|
SHOW_SONGS(R.drawable.ic_song, R.string.lbl_songs),
|
||||||
SHOW_ARTISTS,
|
SHOW_ALBUMS(R.drawable.ic_album, R.string.lbl_albums),
|
||||||
SHOW_ALBUMS,
|
SHOW_ARTISTS(R.drawable.ic_artist, R.string.lbl_artists),
|
||||||
SHOW_SONGS;
|
SHOW_GENRES(R.drawable.ic_genre, R.string.lbl_genres);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CONST_NULL = 0xA107
|
private const val CONST_NULL = 0xA107
|
||||||
|
|
|
@ -36,7 +36,8 @@ import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
||||||
* packing what could be considered 3 or 4 widgets into a single responsive widget. More specifically:
|
* packing what could be considered multiple widgets into a single responsive widget. More
|
||||||
|
* specifically:
|
||||||
*
|
*
|
||||||
* - For widgets Wx2 or higher, show an expanded view with album art and basic controls
|
* - For widgets Wx2 or higher, show an expanded view with album art and basic controls
|
||||||
* - For widgets 4x2 or higher, show a complete view with all playback controls
|
* - For widgets 4x2 or higher, show a complete view with all playback controls
|
||||||
|
@ -115,7 +116,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// / --- INTERNAL METHODS ---
|
// --- INTERNAL METHODS ---
|
||||||
|
|
||||||
private fun requestUpdate(context: Context) {
|
private fun requestUpdate(context: Context) {
|
||||||
logD("Sending update intent to PlaybackService")
|
logD("Sending update intent to PlaybackService")
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:alpha="0.24" android:color="?attr/colorOnSurface" android:state_enabled="false" />
|
|
||||||
<item android:color="?attr/colorControlNormal" />
|
|
||||||
</selector>
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:alpha="0.24" android:color="?attr/colorOnSurface" android:state_enabled="false" />
|
||||||
<item android:color="?attr/colorPrimary" android:state_activated="true" />
|
<item android:color="?attr/colorPrimary" android:state_activated="true" />
|
||||||
<item android:color="?attr/colorControlNormal" />
|
<item android:color="?attr/colorControlNormal" />
|
||||||
</selector>
|
</selector>
|
|
@ -2,7 +2,7 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="@color/overlay_disabled"
|
android:tint="@color/sel_accented"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
android:paddingTop="@dimen/spacing_medium"
|
android:paddingTop="@dimen/spacing_medium"
|
||||||
android:paddingStart="@dimen/spacing_small"
|
android:paddingStart="@dimen/spacing_small"
|
||||||
android:paddingEnd="@dimen/spacing_small"
|
android:paddingEnd="@dimen/spacing_small"
|
||||||
android:paddingBottom="@dimen/spacing_small"
|
|
||||||
app:layoutManager="org.oxycblt.auxio.accent.AutoGridLayoutManager"
|
app:layoutManager="org.oxycblt.auxio.accent.AutoGridLayoutManager"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
|
app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/accent_header"
|
app:layout_constraintTop_toBottomOf="@+id/accent_header"
|
||||||
|
|
18
app/src/main/res/layout/dialog_tabs.xml
Normal file
18
app/src/main/res/layout/dialog_tabs.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/tab_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:paddingTop="@dimen/spacing_medium"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/accent_header"
|
||||||
|
tools:itemCount="5"
|
||||||
|
tools:listitem="@layout/item_tab" />
|
||||||
|
|
||||||
|
</layout>
|
|
@ -15,7 +15,7 @@
|
||||||
app:liftOnScroll="true"
|
app:liftOnScroll="true"
|
||||||
app:liftOnScrollTargetViewId="@id/search_recycler">
|
app:liftOnScrollTargetViewId="@id/search_recycler">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/search_toolbar"
|
android:id="@+id/search_toolbar"
|
||||||
style="@style/Widget.Auxio.Toolbar.Icon"
|
style="@style/Widget.Auxio.Toolbar.Icon"
|
||||||
app:menu="@menu/menu_search">
|
app:menu="@menu/menu_search">
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
</org.oxycblt.auxio.ui.LiftAppBarLayout>
|
</org.oxycblt.auxio.ui.LiftAppBarLayout>
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
android:src="@drawable/ic_handle"
|
android:src="@drawable/ic_handle"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/album_cover"
|
app:layout_constraintBottom_toBottomOf="@+id/album_cover"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/song_name" />
|
app:layout_constraintTop_toTopOf="@+id/album_cover" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
57
app/src/main/res/layout/item_tab.xml
Normal file
57
app/src/main/res/layout/item_tab.xml
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
style="@style/Widget.Auxio.ItemLayout"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="0dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/tab_icon"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:padding="@dimen/spacing_small"
|
||||||
|
android:layout_width="@dimen/size_btn_small"
|
||||||
|
android:layout_height="@dimen/size_btn_small"
|
||||||
|
android:layout_margin="@dimen/spacing_small"
|
||||||
|
android:src="@drawable/ic_artist"
|
||||||
|
app:tint="@color/sel_accented"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="contentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tab_name"
|
||||||
|
style="@style/Widget.Auxio.TextView.Item.Primary"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/spacing_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="@null"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/tab_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Artist" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/tab_drag_handle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/desc_tab_handle"
|
||||||
|
android:focusable="true"
|
||||||
|
android:minWidth="@dimen/size_btn_small"
|
||||||
|
android:minHeight="@dimen/size_btn_small"
|
||||||
|
android:paddingStart="@dimen/spacing_medium"
|
||||||
|
android:paddingEnd="@dimen/spacing_medium"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_handle"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/tab_icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/tab_icon" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
|
@ -8,7 +8,7 @@
|
||||||
android:theme="@style/Theme.Widget"
|
android:theme="@style/Theme.Widget"
|
||||||
tools:ignore="Overdraw">
|
tools:ignore="Overdraw">
|
||||||
|
|
||||||
<ImageView
|
<android.widget.ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:alpha="0.3"
|
android:alpha="0.3"
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/ic_song" />
|
android:src="@drawable/ic_song" />
|
||||||
|
|
||||||
<TextView
|
<android.widget.TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
android:theme="@style/Theme.Widget">
|
android:theme="@style/Theme.Widget">
|
||||||
|
|
||||||
<ImageView
|
<android.widget.ImageView
|
||||||
android:id="@+id/widget_cover"
|
android:id="@+id/widget_cover"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
@ -16,29 +16,29 @@
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/ic_song" />
|
android:src="@drawable/ic_song" />
|
||||||
|
|
||||||
<LinearLayout style="@style/Widget.Auxio.AppWidget.Panel">
|
<android.widget.LinearLayout style="@style/Widget.Auxio.AppWidget.Panel">
|
||||||
|
|
||||||
<TextView
|
<android.widget.TextView
|
||||||
android:id="@+id/widget_song"
|
android:id="@+id/widget_song"
|
||||||
style="@style/Widget.Auxio.TextView.Primary.AppWidget"
|
style="@style/Widget.Auxio.TextView.Primary.AppWidget"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/def_widget_song" />
|
android:text="@string/def_widget_song" />
|
||||||
|
|
||||||
<TextView
|
<android.widget.TextView
|
||||||
android:id="@+id/widget_artist"
|
android:id="@+id/widget_artist"
|
||||||
style="@style/Widget.Auxio.TextView.Secondary.AppWidget"
|
style="@style/Widget.Auxio.TextView.Secondary.AppWidget"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/def_widget_artist" />
|
android:text="@string/def_widget_artist" />
|
||||||
|
|
||||||
<LinearLayout
|
<android.widget.LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/spacing_medium"
|
android:layout_marginTop="@dimen/spacing_medium"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_loop"
|
android:id="@+id/widget_loop"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
android:contentDescription="@string/desc_change_loop"
|
android:contentDescription="@string/desc_change_loop"
|
||||||
android:src="@drawable/ic_loop" />
|
android:src="@drawable/ic_loop" />
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_skip_prev"
|
android:id="@+id/widget_skip_prev"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
android:contentDescription="@string/desc_skip_prev"
|
android:contentDescription="@string/desc_skip_prev"
|
||||||
android:src="@drawable/ic_skip_prev" />
|
android:src="@drawable/ic_skip_prev" />
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_play_pause"
|
android:id="@+id/widget_play_pause"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/sel_playing_state" />
|
android:src="@drawable/sel_playing_state" />
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_skip_next"
|
android:id="@+id/widget_skip_next"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
android:contentDescription="@string/desc_skip_next"
|
android:contentDescription="@string/desc_skip_next"
|
||||||
android:src="@drawable/ic_skip_next" />
|
android:src="@drawable/ic_skip_next" />
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_shuffle"
|
android:id="@+id/widget_shuffle"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
@ -83,8 +83,8 @@
|
||||||
android:contentDescription="@string/desc_shuffle"
|
android:contentDescription="@string/desc_shuffle"
|
||||||
android:src="@drawable/ic_shuffle" />
|
android:src="@drawable/ic_shuffle" />
|
||||||
|
|
||||||
</LinearLayout>
|
</android.widget.LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</android.widget.LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
android:theme="@style/Theme.Widget">
|
android:theme="@style/Theme.Widget">
|
||||||
|
|
||||||
<ImageView
|
<android.widget.ImageView
|
||||||
android:id="@+id/widget_cover"
|
android:id="@+id/widget_cover"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
@ -16,28 +16,28 @@
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/ic_song" />
|
android:src="@drawable/ic_song" />
|
||||||
|
|
||||||
<LinearLayout style="@style/Widget.Auxio.AppWidget.Panel">
|
<android.widget.LinearLayout style="@style/Widget.Auxio.AppWidget.Panel">
|
||||||
|
|
||||||
<TextView
|
<android.widget.TextView
|
||||||
android:id="@+id/widget_song"
|
android:id="@+id/widget_song"
|
||||||
style="@style/Widget.Auxio.TextView.Primary.AppWidget"
|
style="@style/Widget.Auxio.TextView.Primary.AppWidget"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/def_widget_song" />
|
android:text="@string/def_widget_song" />
|
||||||
|
|
||||||
<TextView
|
<android.widget.TextView
|
||||||
android:id="@+id/widget_artist"
|
android:id="@+id/widget_artist"
|
||||||
style="@style/Widget.Auxio.TextView.Secondary.AppWidget"
|
style="@style/Widget.Auxio.TextView.Secondary.AppWidget"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/def_widget_artist" />
|
android:text="@string/def_widget_artist" />
|
||||||
|
|
||||||
<LinearLayout
|
<android.widget.LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/spacing_medium">
|
android:layout_marginTop="@dimen/spacing_medium">
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_skip_prev"
|
android:id="@+id/widget_skip_prev"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
android:contentDescription="@string/desc_skip_prev"
|
android:contentDescription="@string/desc_skip_prev"
|
||||||
android:src="@drawable/ic_skip_prev" />
|
android:src="@drawable/ic_skip_prev" />
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_play_pause"
|
android:id="@+id/widget_play_pause"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/ic_play" />
|
android:src="@drawable/ic_play" />
|
||||||
|
|
||||||
<ImageButton
|
<android.widget.ImageButton
|
||||||
android:id="@+id/widget_skip_next"
|
android:id="@+id/widget_skip_next"
|
||||||
style="@style/Widget.Auxio.Button.AppWidget"
|
style="@style/Widget.Auxio.Button.AppWidget"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
@ -64,8 +64,8 @@
|
||||||
android:contentDescription="@string/desc_skip_next"
|
android:contentDescription="@string/desc_skip_next"
|
||||||
android:src="@drawable/ic_skip_next" />
|
android:src="@drawable/ic_skip_next" />
|
||||||
|
|
||||||
</LinearLayout>
|
</android.widget.LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</android.widget.LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -53,8 +53,6 @@
|
||||||
<item name="android:textColorPrimaryInverseDisableOnly">@color/m3_dynamic_primary_text_disable_only</item>
|
<item name="android:textColorPrimaryInverseDisableOnly">@color/m3_dynamic_primary_text_disable_only</item>
|
||||||
<item name="android:textColorHint">@color/m3_dynamic_dark_hint_foreground</item>
|
<item name="android:textColorHint">@color/m3_dynamic_dark_hint_foreground</item>
|
||||||
<item name="android:textColorHintInverse">@color/m3_dynamic_hint_foreground</item>
|
<item name="android:textColorHintInverse">@color/m3_dynamic_hint_foreground</item>
|
||||||
<item name="android:textColorHighlight">@color/m3_dynamic_dark_highlighted_text</item>
|
|
||||||
<item name="android:textColorHighlightInverse">@color/m3_dynamic_highlighted_text</item>
|
|
||||||
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_dark_default_color_primary_text</item>
|
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_dark_default_color_primary_text</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
4
app/src/main/res/values-v31/bools.xml
Normal file
4
app/src/main/res/values-v31/bools.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<bool name="enable_theme_settings">false</bool>
|
||||||
|
</resources>
|
|
@ -53,8 +53,6 @@
|
||||||
<item name="android:textColorPrimaryInverseDisableOnly">@color/m3_dynamic_dark_primary_text_disable_only</item>
|
<item name="android:textColorPrimaryInverseDisableOnly">@color/m3_dynamic_dark_primary_text_disable_only</item>
|
||||||
<item name="android:textColorHint">@color/m3_dynamic_hint_foreground</item>
|
<item name="android:textColorHint">@color/m3_dynamic_hint_foreground</item>
|
||||||
<item name="android:textColorHintInverse">@color/m3_dynamic_dark_hint_foreground</item>
|
<item name="android:textColorHintInverse">@color/m3_dynamic_dark_hint_foreground</item>
|
||||||
<item name="android:textColorHighlight">@color/m3_dynamic_highlighted_text</item>
|
|
||||||
<item name="android:textColorHighlightInverse">@color/m3_dynamic_dark_highlighted_text</item>
|
|
||||||
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_default_color_primary_text</item>
|
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_default_color_primary_text</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
4
app/src/main/res/values/bools.xml
Normal file
4
app/src/main/res/values/bools.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<bool name="enable_theme_settings">true</bool>
|
||||||
|
</resources>
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
<!-- Size Namespace | Width & Heights for UI elements -->
|
<!-- Size Namespace | Width & Heights for UI elements -->
|
||||||
<dimen name="size_btn_small">48dp</dimen>
|
<dimen name="size_btn_small">48dp</dimen>
|
||||||
|
<dimen name="size_btn_medium">56dp</dimen>
|
||||||
<dimen name="size_btn_large">64dp</dimen>
|
<dimen name="size_btn_large">64dp</dimen>
|
||||||
|
|
||||||
<dimen name="size_cover_compact">48dp</dimen>
|
<dimen name="size_cover_compact">48dp</dimen>
|
||||||
|
|
|
@ -70,6 +70,8 @@
|
||||||
<string name="setting_black_mode_desc">Use a pure-black dark theme</string>
|
<string name="setting_black_mode_desc">Use a pure-black dark theme</string>
|
||||||
|
|
||||||
<string name="set_display">Display</string>
|
<string name="set_display">Display</string>
|
||||||
|
<string name="set_lib_tabs">Library tabs</string>
|
||||||
|
<string name="set_lib_tabs_desc">Change visibility and order of library tabs</string>
|
||||||
<string name="set_show_covers">Show album covers</string>
|
<string name="set_show_covers">Show album covers</string>
|
||||||
<string name="set_show_covers_desc">Turn off to save memory usage</string>
|
<string name="set_show_covers_desc">Turn off to save memory usage</string>
|
||||||
<string name="set_quality_covers">Ignore MediaStore covers</string>
|
<string name="set_quality_covers">Ignore MediaStore covers</string>
|
||||||
|
@ -120,7 +122,8 @@
|
||||||
|
|
||||||
<string name="desc_clear_user_queue">Clear queue</string>
|
<string name="desc_clear_user_queue">Clear queue</string>
|
||||||
<string name="desc_clear_queue_item">Remove this queue item</string>
|
<string name="desc_clear_queue_item">Remove this queue item</string>
|
||||||
<string name="desc_queue_handle">Move queue song</string>
|
<string name="desc_queue_handle">Move this queue song</string>
|
||||||
|
<string name="desc_tab_handle">Move this tab</string>
|
||||||
<string name="desc_clear_search">Clear search query</string>
|
<string name="desc_clear_search">Clear search query</string>
|
||||||
<string name="desc_blacklist_delete">Remove excluded directory</string>
|
<string name="desc_blacklist_delete">Remove excluded directory</string>
|
||||||
|
|
||||||
|
@ -162,6 +165,7 @@
|
||||||
<string name="fmt_next_from">Next From: %s</string>
|
<string name="fmt_next_from">Next From: %s</string>
|
||||||
<string name="fmt_songs_loaded">Songs loaded: %d</string>
|
<string name="fmt_songs_loaded">Songs loaded: %d</string>
|
||||||
|
|
||||||
|
|
||||||
<plurals name="fmt_song_count">
|
<plurals name="fmt_song_count">
|
||||||
<item quantity="one">%d Song</item>
|
<item quantity="one">%d Song</item>
|
||||||
<item quantity="other">%d Songs</item>
|
<item quantity="other">%d Songs</item>
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_width">wrap_content</item>
|
<item name="android:layout_width">wrap_content</item>
|
||||||
<item name="android:fontFamily">@font/inter_semibold</item>
|
<item name="android:fontFamily">@font/inter_semibold</item>
|
||||||
<item name="android:textColor">?attr/colorPrimary</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Custom button style that eliminates the weird margin that the neutral button has -->
|
<!-- Custom button style that eliminates the weird margin that the neutral button has -->
|
||||||
|
|
|
@ -18,16 +18,16 @@
|
||||||
<item name="colorOnTertiaryContainer">?attr/colorOnPrimaryContainer</item>
|
<item name="colorOnTertiaryContainer">?attr/colorOnPrimaryContainer</item>
|
||||||
|
|
||||||
<item name="colorSurface">@color/surface</item>
|
<item name="colorSurface">@color/surface</item>
|
||||||
<item name="colorAccent">?attr/colorPrimary</item>
|
|
||||||
<item name="colorControlNormal">@color/control</item>
|
<item name="colorControlNormal">@color/control</item>
|
||||||
<item name="colorControlHighlight">@color/overlay_selection</item>
|
|
||||||
<item name="colorControlActivated">?attr/colorPrimary</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Base theme -->
|
<!-- Base theme -->
|
||||||
<style name="Theme.Auxio.App" parent="Theme.Auxio.V31">
|
<style name="Theme.Auxio.App" parent="Theme.Auxio.V31">
|
||||||
<!-- Values -->
|
<!-- Values -->
|
||||||
<item name="colorOutline">@color/overlay_stroke</item>
|
<item name="colorOutline">@color/overlay_stroke</item>
|
||||||
|
<item name="colorAccent">?attr/colorSecondary</item>
|
||||||
|
<item name="colorControlHighlight">@color/overlay_selection</item>
|
||||||
|
<item name="colorControlActivated">?attr/colorSecondary</item>
|
||||||
|
|
||||||
<!-- Android component magic -->
|
<!-- Android component magic -->
|
||||||
<item name="android:textColorHighlight">@color/overlay_text_highlight</item>
|
<item name="android:textColorHighlight">@color/overlay_text_highlight</item>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<!-- Base toolbar style -->
|
<!-- Base toolbar style -->
|
||||||
<style name="Widget.Auxio.Toolbar" parent="ThemeOverlay.Material3.ActionBar">
|
<style name="Widget.Auxio.Toolbar" parent="ThemeOverlay.Material3.ActionBar">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
<item name="android:layout_height">?android:attr/actionBarSize</item>
|
<item name="android:layout_height">@dimen/size_btn_medium</item>
|
||||||
<item name="titleTextAppearance">@style/TextAppearance.ToolbarTitle</item>
|
<item name="titleTextAppearance">@style/TextAppearance.ToolbarTitle</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
<PreferenceCategory
|
|
||||||
app:layout="@layout/item_header"
|
|
||||||
app:title="@string/set_display">
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:defaultValue="true"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_SHOW_COVERS"
|
|
||||||
app:summary="@string/set_show_covers_desc"
|
|
||||||
app:title="@string/set_show_covers" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:defaultValue="false"
|
|
||||||
app:dependency="KEY_SHOW_COVERS"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_QUALITY_COVERS"
|
|
||||||
app:summary="@string/set_quality_covers_desc"
|
|
||||||
app:title="@string/set_quality_covers" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:allowDividerBelow="false"
|
|
||||||
app:defaultValue="false"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_ALT_NOTIF_ACTION"
|
|
||||||
app:summaryOff="@string/set_alt_loop"
|
|
||||||
app:summaryOn="@string/set_alt_shuffle"
|
|
||||||
app:title="@string/set_alt_action" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
app:layout="@layout/item_header"
|
|
||||||
app:title="@string/set_audio">
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:defaultValue="true"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_AUDIO_FOCUS"
|
|
||||||
app:summary="@string/set_focus_desc"
|
|
||||||
app:title="@string/set_focus" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:allowDividerBelow="false"
|
|
||||||
app:defaultValue="true"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_PLUG_MGT"
|
|
||||||
app:summary="@string/set_plug_mgt_desc"
|
|
||||||
app:title="@string/set_plug_mgt" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
app:layout="@layout/item_header"
|
|
||||||
app:title="@string/set_behavior">
|
|
||||||
|
|
||||||
<org.oxycblt.auxio.settings.IntListPreference
|
|
||||||
app:defaultValue="@integer/play_mode_songs"
|
|
||||||
app:entries="@array/entries_song_playback_mode"
|
|
||||||
app:entryValues="@array/values_song_playback_mode"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_SONG_PLAY_MODE2"
|
|
||||||
app:title="@string/set_song_mode"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:defaultValue="true"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_KEEP_SHUFFLE"
|
|
||||||
app:summary="@string/set_keep_shuffle_desc"
|
|
||||||
app:title="@string/set_keep_shuffle" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:allowDividerBelow="false"
|
|
||||||
app:defaultValue="true"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_PREV_REWIND"
|
|
||||||
app:summary="@string/set_rewind_prev_desc"
|
|
||||||
app:title="@string/set_rewind_prev" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
app:allowDividerBelow="false"
|
|
||||||
app:defaultValue="false"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_LOOP_PAUSE"
|
|
||||||
app:summary="@string/set_loop_pause_desc"
|
|
||||||
app:title="@string/set_loop_pause" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
app:layout="@layout/item_header"
|
|
||||||
app:title="@string/set_content">
|
|
||||||
|
|
||||||
<Preference
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_SAVE_STATE"
|
|
||||||
app:summary="@string/set_save_desc"
|
|
||||||
app:title="@string/set_save" />
|
|
||||||
|
|
||||||
<Preference
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="KEY_BLACKLIST"
|
|
||||||
app:summary="@string/set_excluded_desc"
|
|
||||||
app:title="@string/set_excluded" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
</PreferenceScreen>
|
|
|
@ -2,9 +2,10 @@
|
||||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
app:layout="@layout/item_header"
|
app:layout="@layout/item_header"
|
||||||
|
app:isPreferenceVisible="@bool/enable_theme_settings"
|
||||||
app:title="@string/set_ui">
|
app:title="@string/set_ui">
|
||||||
|
|
||||||
<org.oxycblt.auxio.settings.IntListPreference
|
<org.oxycblt.auxio.settings.pref.IntListPreference
|
||||||
app:defaultValue="@integer/theme_auto"
|
app:defaultValue="@integer/theme_auto"
|
||||||
app:entries="@array/entires_theme"
|
app:entries="@array/entires_theme"
|
||||||
app:entryValues="@array/values_theme"
|
app:entryValues="@array/values_theme"
|
||||||
|
@ -14,7 +15,6 @@
|
||||||
app:title="@string/set_theme" />
|
app:title="@string/set_theme" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
app:allowDividerBelow="false"
|
|
||||||
app:icon="@drawable/ic_accent"
|
app:icon="@drawable/ic_accent"
|
||||||
app:key="KEY_ACCENT2"
|
app:key="KEY_ACCENT2"
|
||||||
app:summary="@string/clr_blue"
|
app:summary="@string/clr_blue"
|
||||||
|
@ -34,6 +34,12 @@
|
||||||
app:layout="@layout/item_header"
|
app:layout="@layout/item_header"
|
||||||
app:title="@string/set_display">
|
app:title="@string/set_display">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="KEY_LIB_TABS"
|
||||||
|
app:title="@string/set_lib_tabs"
|
||||||
|
app:summary="@string/set_lib_tabs_desc"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:defaultValue="true"
|
app:defaultValue="true"
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
|
@ -85,7 +91,7 @@
|
||||||
app:layout="@layout/item_header"
|
app:layout="@layout/item_header"
|
||||||
app:title="@string/set_behavior">
|
app:title="@string/set_behavior">
|
||||||
|
|
||||||
<org.oxycblt.auxio.settings.IntListPreference
|
<org.oxycblt.auxio.settings.pref.IntListPreference
|
||||||
app:defaultValue="@integer/play_mode_songs"
|
app:defaultValue="@integer/play_mode_songs"
|
||||||
app:entries="@array/entries_song_playback_mode"
|
app:entries="@array/entries_song_playback_mode"
|
||||||
app:entryValues="@array/values_song_playback_mode"
|
app:entryValues="@array/values_song_playback_mode"
|
||||||
|
|
Loading…
Reference in a new issue