home: merge all library views into home

Merge LibraryFragment, SongsFragment, and others into a new fragment
called HomeFragment. This is the beginning of the Auxio UI overhaul.
This has caused some regressions here and there, but these will be
rectified over time.
This commit is contained in:
OxygenCobalt 2021-08-21 19:49:25 -06:00
parent e83867270b
commit a3e7cb93aa
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
33 changed files with 528 additions and 1097 deletions

View file

@ -18,32 +18,21 @@
package org.oxycblt.auxio
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.ColorUtils
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController
import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
/**
* The primary "Home" [Fragment] for Auxio.
* A wrapper around the home
*/
class MainFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
@ -52,51 +41,10 @@ class MainFragment : Fragment() {
): View {
val binding = FragmentMainBinding.inflate(inflater)
val colorActive = Accent.get().color.resolveColor(requireContext())
val colorInactive = ColorUtils.setAlphaComponent(colorActive, 150)
// Set up the tints for the navigation icons + text
val navTints = ColorStateList(
arrayOf(
intArrayOf(-android.R.attr.state_checked),
intArrayOf(android.R.attr.state_checked)
),
intArrayOf(colorInactive, colorActive)
)
val navController = (
childFragmentManager.findFragmentById(R.id.explore_nav_host)
as NavHostFragment?
)?.findNavController()
// --- UI SETUP ---
binding.lifecycleOwner = viewLifecycleOwner
// Speed up the slide-in effect on the controls view, solely to improve the UX
// and maybe hide the problem where the main view will snap-shrink before the compact
// view can slide.
(binding.controlsContainer as ViewGroup).layoutTransition.setDuration(150)
binding.navBar.apply {
itemIconTintList = navTints
itemTextColor = navTints
if (requireContext().isTablet() && !requireContext().isLandscape()) {
labelVisibilityMode = BottomNavigationView.LABEL_VISIBILITY_LABELED
}
navController?.let { controller ->
binding.navBar.setOnItemSelectedListener { item ->
navigateWithItem(controller, item)
}
}
// BottomNavigationView is a special little snowflake and doesn't like it when
// we set the background in XML
setBackgroundColor(R.attr.colorSurface.resolveAttr(requireContext()))
}
// --- VIEWMODEL SETUP ---
// Change CompactPlaybackFragment's visibility here so that an animation occurs.
@ -106,49 +54,11 @@ class MainFragment : Fragment() {
handleCompactPlaybackVisibility(binding, song)
}
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
if (item != null && navController != null) {
val curDest = navController.currentDestination?.id
// SongsFragment and SettingsFragment have no navigation pathways, so correct
// them to the library tab instead.
if (curDest == R.id.songs_fragment || curDest == R.id.settings_fragment) {
binding.navBar.selectedItemId = R.id.library_fragment
}
}
}
playbackModel.setupPlayback(requireContext())
logD("Fragment Created.")
return binding.root
}
/**
* Custom navigator code that has proper animations, unlike BottomNavigationView.setupWithNavController().
*/
private fun navigateWithItem(navController: NavController, item: MenuItem): Boolean {
if (navController.currentDestination!!.id != item.itemId) {
val options = NavOptions.Builder()
.setLaunchSingleTop(true)
.setEnterAnim(R.animator.nav_default_enter_anim)
.setExitAnim(R.animator.nav_default_exit_anim)
.setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
.setPopExitAnim(R.animator.nav_default_pop_exit_anim)
.build()
return try {
navController.navigate(item.itemId, null, options)
true
} catch (e: IllegalArgumentException) {
false
}
}
return false
}
/**
* Handle the visibility of CompactPlaybackFragment. Done here so that there's a nice animation.
*/
@ -156,13 +66,13 @@ class MainFragment : Fragment() {
if (song == null) {
logD("Hiding CompactPlaybackFragment since no song is being played.")
binding.compactPlayback.visibility = if (requireContext().isLandscape()) {
binding.mainPlayback.visibility = if (requireContext().isLandscape()) {
View.INVISIBLE
} else {
View.GONE
}
} else {
binding.compactPlayback.visibility = View.VISIBLE
binding.mainPlayback.visibility = View.VISIBLE
}
}
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Auxio Project
* LibraryAdapter.kt is part of Auxio.
* HomeAdapter.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
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.library
package org.oxycblt.auxio.home
import android.annotation.SuppressLint
import android.view.View
@ -24,22 +24,25 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
/**
* An adapter for displaying library items. Supports [Parent]s only.
* @author OxygenCobalt
*/
class LibraryAdapter(
private val doOnClick: (data: Parent) -> Unit,
private val doOnLongClick: (view: View, data: Parent) -> Unit
class HomeAdapter(
private val doOnClick: (data: BaseModel) -> Unit,
private val doOnLongClick: (view: View, data: BaseModel) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var data = listOf<Parent>()
private var data = listOf<BaseModel>()
override fun getItemCount(): Int = data.size
@ -48,6 +51,8 @@ class LibraryAdapter(
is Genre -> GenreViewHolder.ITEM_TYPE
is Artist -> ArtistViewHolder.ITEM_TYPE
is Album -> AlbumViewHolder.ITEM_TYPE
is Song -> SongViewHolder.ITEM_TYPE
else -> error("Unsupported item ${data[position]::class.simpleName}")
}
}
@ -65,6 +70,10 @@ class LibraryAdapter(
parent.context, doOnClick, doOnLongClick
)
SongViewHolder.ITEM_TYPE -> SongViewHolder.from(
parent.context, doOnClick, doOnLongClick
)
else -> error("Invalid viewholder item type.")
}
}
@ -74,6 +83,7 @@ class LibraryAdapter(
is Genre -> (holder as GenreViewHolder).bind(item)
is Artist -> (holder as ArtistViewHolder).bind(item)
is Album -> (holder as AlbumViewHolder).bind(item)
is Song -> (holder as SongViewHolder).bind(item)
}
}
@ -81,7 +91,7 @@ class LibraryAdapter(
* Update the data with [newData]. [notifyDataSetChanged] will be called.
*/
@SuppressLint("NotifyDataSetChanged")
fun updateData(newData: List<Parent>) {
fun updateData(newData: List<BaseModel>) {
data = newData
notifyDataSetChanged()

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2021 Auxio Project
* MainFragment.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.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeBinding
import org.oxycblt.auxio.home.pager.AlbumListFragment
import org.oxycblt.auxio.home.pager.ArtistListFragment
import org.oxycblt.auxio.home.pager.GenreListFragment
import org.oxycblt.auxio.home.pager.SongListFragment
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
/**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
* views for each respective fragment.
* TODO: Re-add sorting (but new and improved)
* @author OxygenCobalt
*/
class HomeFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentHomeBinding.inflate(inflater)
// --- UI SETUP ---
binding.lifecycleOwner = viewLifecycleOwner
binding.homeToolbar.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_settings -> {
parentFragment?.parentFragment?.findNavController()?.navigate(
MainFragmentDirections.actionShowSettings()
)
}
R.id.action_search -> {
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
}
}
true
}
binding.homePager.adapter = HomePagerAdapter()
TabLayoutMediator(binding.homeTabs, binding.homePager) { tab, pos ->
val labelRes = when (pos) {
0 -> R.string.lbl_songs
1 -> R.string.lbl_albums
2 -> R.string.lbl_artists
3 -> R.string.lbl_genres
else -> error("Unreachable")
}
tab.setText(labelRes)
}.attach()
// --- VIEWMODEL SETUP ---
playbackModel.setupPlayback(requireContext())
logD("Fragment Created.")
return binding.root
}
private inner class HomePagerAdapter :
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> SongListFragment()
1 -> AlbumListFragment()
2 -> ArtistListFragment()
3 -> GenreListFragment()
else -> error("Unreachable")
}
}
override fun getItemCount(): Int = 4
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2021 Auxio Project
* GenreListFragment.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.home.pager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeAdapter
import org.oxycblt.auxio.home.HomeFragmentDirections
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.newMenu
class AlbumListFragment : Fragment() {
private val playbackModel: PlaybackViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentHomeListBinding.inflate(inflater)
val adapter = HomeAdapter(
doOnClick = { item ->
HomeFragmentDirections.actionShowAlbum(item.id)
},
::newMenu
)
adapter.updateData(MusicStore.getInstance().albums)
// --- UI SETUP ---
binding.homeRecycler.adapter = adapter
return binding.root
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2021 Auxio Project
* GenreListFragment.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.home.pager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeAdapter
import org.oxycblt.auxio.home.HomeFragmentDirections
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.newMenu
class ArtistListFragment : Fragment() {
private val playbackModel: PlaybackViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentHomeListBinding.inflate(inflater)
val adapter = HomeAdapter(
doOnClick = { item ->
HomeFragmentDirections.actionShowArtist(item.id)
},
::newMenu
)
adapter.updateData(MusicStore.getInstance().artists)
// --- UI SETUP ---
binding.homeRecycler.adapter = adapter
return binding.root
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2021 Auxio Project
* GenreListFragment.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.home.pager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeAdapter
import org.oxycblt.auxio.home.HomeFragmentDirections
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.newMenu
class GenreListFragment : Fragment() {
private val playbackModel: PlaybackViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentHomeListBinding.inflate(inflater)
val adapter = HomeAdapter(
doOnClick = { item ->
HomeFragmentDirections.actionShowGenre(item.id)
},
::newMenu
)
adapter.updateData(MusicStore.getInstance().genres)
// --- UI SETUP ---
binding.homeRecycler.adapter = adapter
return binding.root
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2021 Auxio Project
* GenreListFragment.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.home.pager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeAdapter
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.newMenu
class SongListFragment : Fragment() {
private val playbackModel: PlaybackViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentHomeListBinding.inflate(inflater)
val adapter = HomeAdapter(
doOnClick = { item ->
playbackModel.playSong(item as Song)
},
::newMenu
)
adapter.updateData(MusicStore.getInstance().songs)
// --- UI SETUP ---
binding.homeRecycler.adapter = adapter
return binding.root
}
}

View file

@ -1,142 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* LibraryFragment.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.library
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLibraryBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.sliceArticle
import org.oxycblt.auxio.spans
import org.oxycblt.auxio.ui.newMenu
/**
* A [Fragment] that shows a custom list of [Genre], [Artist], or [Album] data. Also allows for
* search functionality.
* @author OxygenCobalt
*/
class LibraryFragment : Fragment() {
private val libraryModel: LibraryViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentLibraryBinding.inflate(inflater)
val libraryAdapter = LibraryAdapter(::navToDetail, ::newMenu)
// --- UI SETUP ---
binding.lifecycleOwner = viewLifecycleOwner
binding.libraryToolbar.apply {
menu.findItem(libraryModel.sortMode.toMenuId()).isChecked = true
setOnMenuItemClickListener { item ->
if (item.itemId != R.id.submenu_sorting) {
libraryModel.updateSortMode(item.itemId)
item.isChecked = true
true
} else {
false
}
}
}
binding.libraryRecycler.apply {
adapter = libraryAdapter
setHasFixedSize(true)
if (spans != 1) {
layoutManager = GridLayoutManager(requireContext(), spans)
}
}
binding.libraryFastScroll.setup(binding.libraryRecycler) { pos ->
val item = libraryModel.libraryData.value!![pos]
val char = item.displayName.sliceArticle().first().uppercaseChar()
if (char.isDigit()) '#' else char
}
// --- VIEWMODEL SETUP ---
libraryModel.libraryData.observe(viewLifecycleOwner) { data ->
libraryAdapter.updateData(data)
}
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
if (item != null) {
libraryModel.setNavigating(false)
if (item is Parent) {
navToDetail(item)
} else if (item is Song) {
navToDetail(item.album)
}
}
}
logD("Fragment created.")
return binding.root
}
override fun onResume() {
super.onResume()
libraryModel.setNavigating(false)
}
/**
* Navigate to the detail UI for a [parent].
*/
private fun navToDetail(parent: Parent) {
requireView().rootView.clearFocus()
if (!libraryModel.isNavigating) {
libraryModel.setNavigating(true)
logD("Navigating to the detail fragment for ${parent.name}")
findNavController().navigate(
when (parent) {
is Genre -> LibraryFragmentDirections.actionShowGenre(parent.id)
is Artist -> LibraryFragmentDirections.actionShowArtist(parent.id)
is Album -> LibraryFragmentDirections.actionShowAlbum(parent.id)
}
)
}
}
}

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* LibraryViewModel.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.library
import androidx.annotation.IdRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.settings.SettingsManager
/**
* A [ViewModel] that manages what [LibraryFragment] is currently showing, and also the search
* functionality.
* @author OxygenCobalt
*/
class LibraryViewModel : ViewModel(), SettingsManager.Callback {
private val mLibraryData = MutableLiveData(listOf<Parent>())
val libraryData: LiveData<List<Parent>> get() = mLibraryData
private var mSortMode = SortMode.ALPHA_DOWN
val sortMode: SortMode get() = mSortMode
private var mDisplayMode = DisplayMode.SHOW_ARTISTS
private var mIsNavigating = false
val isNavigating: Boolean get() = mIsNavigating
private val settingsManager = SettingsManager.getInstance()
private val musicStore = MusicStore.getInstance()
init {
settingsManager.addCallback(this)
// Set up the display/sort modes
mDisplayMode = settingsManager.libraryDisplayMode
mSortMode = settingsManager.librarySortMode
// Handle "NONE" SortMode that was removed in 1.4.1
if (mSortMode == SortMode.NONE) {
mSortMode = SortMode.ALPHA_DOWN
}
updateLibraryData()
}
/**
* Update the current [SortMode] using an menu [itemId].
*/
fun updateSortMode(@IdRes itemId: Int) {
val mode = when (itemId) {
R.id.option_sort_alpha_down -> SortMode.ALPHA_DOWN
R.id.option_sort_alpha_up -> SortMode.ALPHA_UP
else -> SortMode.NONE
}
if (mode != mSortMode) {
mSortMode = mode
settingsManager.librarySortMode = mode
updateLibraryData()
}
}
/**
* Update the current navigation status
*/
fun setNavigating(isNavigating: Boolean) {
mIsNavigating = isNavigating
}
/**
* Shortcut function for updating the library data with the current [SortMode]/[DisplayMode]
*/
private fun updateLibraryData() {
mLibraryData.value = when (mDisplayMode) {
DisplayMode.SHOW_GENRES -> mSortMode.getSortedGenreList(musicStore.genres)
DisplayMode.SHOW_ARTISTS -> mSortMode.getSortedArtistList(musicStore.artists)
DisplayMode.SHOW_ALBUMS -> mSortMode.getSortedAlbumList(musicStore.albums)
else -> error("DisplayMode $mDisplayMode is unsupported.")
}
}
// --- OVERRIDES ---
override fun onCleared() {
super.onCleared()
settingsManager.removeCallback(this)
}
override fun onLibDisplayModeUpdate(displayMode: DisplayMode) {
mDisplayMode = displayMode
updateLibraryData()
}
}

View file

@ -1,293 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* FastScrollView.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.recycler
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.util.TypedValue
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.canScroll
import org.oxycblt.auxio.databinding.ViewFastScrollBinding
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.resolveAttr
import org.oxycblt.auxio.resolveColor
import kotlin.math.ceil
import kotlin.math.min
import kotlin.math.roundToInt
/**
* A view that allows for quick scrolling through a [RecyclerView] with many items. Unlike other
* fast-scrollers, this one displays indicators and a thumb instead of simply a scroll bar.
* This code is fundamentally an adaptation of Reddit's IndicatorFastScroll, albeit specialized
* towards Auxio. The original library is here: https://github.com/reddit/IndicatorFastScroll/
* TODO: Replace this with something similar to AndroidFastScroll [but optimized for Auxio],
* since this thumb view is a blocker to a better sort system.
* @author OxygenCobalt
*/
class FastScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : ConstraintLayout(context, attrs, defStyleAttr) {
// --- UI ---
private val binding = ViewFastScrollBinding.inflate(context.inflater, this, true)
private val thumbAnim: SpringAnimation
// --- RECYCLER ---
private var mRecycler: RecyclerView? = null
private var mGetItem: ((Int) -> Char)? = null
private val mObserver = object : RecyclerView.AdapterDataObserver() {
override fun onChanged() = postIndicatorUpdate()
override fun onItemRangeChanged(
positionStart: Int,
itemCount: Int,
payload: Any?
) = postIndicatorUpdate()
override fun onItemRangeInserted(
positionStart: Int,
itemCount: Int
) = onChanged()
override fun onItemRangeMoved(
fromPosition: Int,
toPosition: Int,
itemCount: Int
) = onChanged()
override fun onItemRangeRemoved(
positionStart: Int,
itemCount: Int
) = onChanged()
}
// --- INDICATORS ---
private data class Indicator(val char: Char, val pos: Int)
private var indicators = listOf<Indicator>()
private val activeColor = Accent.get().color.resolveColor(context)
private val inactiveColor = android.R.attr.textColorSecondary.resolveAttr(context)
// --- STATE ---
private var hasPostedItemUpdate = false
private var wasValidTouch = false
private var lastPos = -1
init {
isFocusableInTouchMode = true
isClickable = true
thumbAnim = SpringAnimation(binding.scrollThumb, DynamicAnimation.TRANSLATION_Y).apply {
spring = SpringForce().also {
it.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
}
}
// Prevent the disappear animation from being displayed on startup by making the thumb
// invisible, it will be made visible once the animation ends
binding.scrollThumb.visibility = View.INVISIBLE
postDelayed(200) {
binding.scrollThumb.visibility = View.VISIBLE
}
}
/**
* Set up this view with a [RecyclerView]. [getItem] is called when the first character
* of a piece of data is needed.
*/
fun setup(recycler: RecyclerView, getItem: (Int) -> Char) {
check(mRecycler == null) { "Only set up this view once." }
mRecycler = recycler
mGetItem = getItem
recycler.adapter?.registerAdapterDataObserver(mObserver)
postIndicatorUpdate()
}
// --- INDICATOR UPDATES ---
private fun postIndicatorUpdate() {
if (!hasPostedItemUpdate) {
hasPostedItemUpdate = true
post {
val recycler = requireNotNull(mRecycler)
if (recycler.isAttachedToWindow && recycler.adapter != null) {
updateIndicators()
binding.scrollIndicatorText.requestLayout()
}
// Hide this view if there is nothing to scroll
isVisible = recycler.canScroll()
hasPostedItemUpdate = false
}
}
}
private fun updateIndicators() {
val recycler = requireNotNull(mRecycler)
val getItem = requireNotNull(mGetItem)
indicators = 0.until(recycler.adapter!!.itemCount).mapNotNull { pos ->
Indicator(getItem(pos), pos)
}.distinctBy { it.char }
val textHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 14F, resources.displayMetrics
)
// If the scroller size is too small to contain all the entries, truncate entries
// so that the fast scroller entries fit. Include the thumb in here so it isn't cut
// off.
val maxEntries = (height - (binding.scrollThumb.height * 2)) / textHeight
if (indicators.size > maxEntries) {
val truncateInterval = ceil(indicators.size / maxEntries).toInt()
check(truncateInterval > 1) {
"Needed to truncate, but truncateInterval was 1 or lower anyway"
}
logD("More entries than screen space, truncating by $truncateInterval.")
indicators = indicators.filterIndexed { index, _ ->
index % truncateInterval == 0
}
}
// Then set it as the unified TextView text, for efficiency purposes.
binding.scrollIndicatorText.text = indicators.joinToString("\n") { indicator ->
indicator.char.toString()
}
}
// --- TOUCH ---
@Suppress("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
super.onTouchEvent(event)
performClick()
val success = handleTouch(event.action, event.x.roundToInt(), event.y.roundToInt())
// Depending on the results, update the visibility of the thumb and the pressed state of
// this view.
binding.scrollThumb.isActivated = success
binding.scrollIndicatorText.isPressed = success
return success
}
private fun handleTouch(action: Int, touchX: Int, touchY: Int): Boolean {
when (action) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
binding.scrollIndicatorText.setTextColor(inactiveColor)
wasValidTouch = false
lastPos = -1
return false
}
// Since this view is unified between the thumb and the indicators, we have
// to check if the initial pointer position was in the indicators to prevent the
// scroll from being triggered outside its bounds.
MotionEvent.ACTION_DOWN -> {
wasValidTouch = binding.scrollIndicatorText.contains(touchX, touchY)
}
}
// Try to figure out which indicator the pointer has landed on
if (binding.scrollIndicatorText.containsY(touchY) && wasValidTouch) {
// Get the touch position in regards to the TextView and the rough text height
val indicatorTouchY = touchY - binding.scrollIndicatorText.top
val textHeight = binding.scrollIndicatorText.height / indicators.size
// Use that to calculate the indicator index, if the calculation is
// invalid just ignore it.
val index = min(indicatorTouchY / textHeight, indicators.lastIndex)
// Also calculate the rough center position of the indicator for the scroll thumb
val centerY = binding.scrollIndicatorText.y + (textHeight / 2) + (index * textHeight)
selectIndicator(indicators[index], centerY)
return true
}
return false
}
private fun selectIndicator(indicator: Indicator, centerY: Float) {
if (indicator.pos != lastPos) {
lastPos = indicator.pos
binding.scrollIndicatorText.setTextColor(activeColor)
// Stop any scroll momentum and snap-scroll to the position
mRecycler?.apply {
stopScroll()
(layoutManager as LinearLayoutManager).scrollToPositionWithOffset(indicator.pos, 0)
}
// Update the thumb position/text
binding.scrollThumbText.text = indicator.char.toString()
thumbAnim.animateToFinalPosition(centerY - (binding.scrollThumb.measuredHeight / 2))
performHapticFeedback(
if (Build.VERSION.SDK_INT >= 27) {
// Dragging across a scroller is closer to moving a text handle
HapticFeedbackConstants.TEXT_HANDLE_MOVE
} else {
HapticFeedbackConstants.KEYBOARD_TAP
}
)
}
}
private fun View.contains(x: Int, y: Int): Boolean {
return x in (left until right) && containsY(y)
}
private fun View.containsY(y: Int): Boolean {
return y in (top until bottom)
}
}

View file

@ -72,6 +72,10 @@ class SearchFragment : Fragment() {
binding.searchToolbar.apply {
menu.findItem(searchModel.filterMode.toId()).isChecked = true
setNavigationOnClickListener {
findNavController().navigateUp()
}
setOnMenuItemClickListener { item ->
if (item.itemId != R.id.submenu_filtering) {
searchModel.updateFilterModeWithId(item.itemId, requireContext())

View file

@ -24,7 +24,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.databinding.FragmentSettingsBinding
/**
@ -39,12 +38,18 @@ class SettingsFragment : Fragment() {
): View {
val binding = FragmentSettingsBinding.inflate(inflater)
binding.settingsToolbar.setOnMenuItemClickListener {
parentFragment?.parentFragment?.findNavController()?.navigate(
MainFragmentDirections.actionShowAbout()
)
binding.settingsToolbar.apply {
setOnMenuItemClickListener {
findNavController().navigate(
SettingsFragmentDirections.actionShowAbout()
)
true
true
}
setNavigationOnClickListener {
findNavController().navigateUp()
}
}
return binding.root

View file

@ -1,49 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* SongsAdapter.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.songs
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
/**
* The adapter for [SongsFragment], shows basic songs without durations.
* @param data List of [Song]s to be shown
* @param doOnClick What to do on a click action
* @param doOnLongClick What to do on a long click action
* @author OxygenCobalt
*/
class SongsAdapter(
private val data: List<Song>,
private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (view: View, data: Song) -> Unit
) : RecyclerView.Adapter<SongViewHolder>() {
override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
return SongViewHolder.from(parent.context, doOnClick, doOnLongClick)
}
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
holder.bind(data[position])
}
}

View file

@ -1,85 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* SongsFragment.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.songs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.GridLayoutManager
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSongsBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.sliceArticle
import org.oxycblt.auxio.spans
import org.oxycblt.auxio.ui.newMenu
/**
* A [Fragment] that shows a list of all songs on the device.
* Contains options to search/shuffle them.
* @author OxygenCobalt
*/
class SongsFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val musicStore = MusicStore.getInstance()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentSongsBinding.inflate(inflater)
val songAdapter = SongsAdapter(musicStore.songs, playbackModel::playSong, ::newMenu)
// --- UI SETUP ---
binding.songToolbar.apply {
setOnMenuItemClickListener {
if (it.itemId == R.id.action_shuffle) {
playbackModel.shuffleAll()
true
} else false
}
}
binding.songRecycler.apply {
adapter = songAdapter
setHasFixedSize(true)
if (spans != 1) {
layoutManager = GridLayoutManager(requireContext(), spans)
}
}
binding.songFastScroll.setup(binding.songRecycler) { pos ->
// Get the first character [respecting articles]
val char = musicStore.songs[pos].name.sliceArticle().first().uppercaseChar()
if (char.isDigit()) '#' else char
}
logD("Fragment created.")
return binding.root
}
}

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white" />
</shape>

View file

@ -1,58 +0,0 @@
<?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"
tools:context=".MainFragment">
<LinearLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:fitsSystemWindows="true"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/explore_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layout_constraintBottom_toTopOf="@+id/controls_container"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_explore"
tools:layout="@layout/fragment_library" />
<LinearLayout
android:id="@+id/controls_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="?android:attr/colorBackground"
android:baselineAligned="false"
android:elevation="8dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_bar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.5"
android:background="?android:attr/colorBackground"
android:elevation="0dp"
app:elevation="0dp"
app:menu="@menu/menu_nav" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/compact_playback"
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
tools:layout="@layout/fragment_compact_playback" />
</LinearLayout>
</LinearLayout>
</layout>

View file

@ -36,8 +36,7 @@
<ImageView
android:id="@+id/about_auxio_icon"
android:layout_width="@dimen/size_app_icon"
android:layout_height="@dimen/size_app_icon"
style="@style/Widget.ImageView.Compact"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@string/desc_auxio_icon"

View file

@ -0,0 +1,50 @@
<?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"
tools:context=".home.HomeFragment">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:fitsSystemWindows="true"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/home_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:liftOnScroll="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/home_toolbar"
style="@style/Widget.Toolbar"
app:menu="@menu/menu_home"
app:layout_scrollFlags="scroll|enterAlways"
app:title="@string/info_app_name" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/home_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabTextAppearance="@style/TextAppearance.TabLayout.Label"
app:tabMode="scrollable"
app:tabContentStart="@dimen/spacing_medium"
app:tabTextColor="?android:attr/textColorPrimary"
app:tabIndicatorColor="?attr/colorAccent" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_artist" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -0,0 +1,13 @@
<?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/home_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_artist" />
</layout>

View file

@ -1,41 +0,0 @@
<?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"
tools:context=".library.LibraryFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/library_toolbar"
style="@style/Widget.Toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu_library"
app:title="@string/lbl_library" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/library_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/library_toolbar"
tools:listitem="@layout/item_artist" />
<org.oxycblt.auxio.recycler.FastScrollView
android:id="@+id/library_fast_scroll"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/library_toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -19,35 +19,15 @@
android:layout_height="0dp"
android:layout_weight="1"
app:navGraph="@navigation/nav_explore"
tools:layout="@layout/fragment_library" />
tools:layout="@layout/fragment_home" />
<org.oxycblt.auxio.ui.SlideLinearLayout
android:id="@+id/controls_container"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_playback"
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="?android:attr/colorBackground"
android:baselineAligned="false"
android:elevation="8dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/compact_playback"
android:name="org.oxycblt.auxio.playback.CompactPlaybackFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/fragment_compact_playback" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?attr/colorSurface"
app:elevation="0dp"
app:menu="@menu/menu_nav" />
</org.oxycblt.auxio.ui.SlideLinearLayout>
android:background="?attr/colorSurface"
tools:layout="@layout/fragment_compact_playback" />
</LinearLayout>
</layout>

View file

@ -16,7 +16,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/search_toolbar"
style="@style/Widget.Toolbar"
style="@style/Widget.Toolbar.Icon"
android:elevation="0dp"
app:layout_scrollFlags="scroll|enterAlways"
app:menu="@menu/menu_search"

View file

@ -7,11 +7,13 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/colorSurface"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/settings_toolbar"
style="@style/Widget.Toolbar"
style="@style/Widget.Toolbar.Icon.Down"
app:menu="@menu/menu_settings"
app:title="@string/set_title" />

View file

@ -1,41 +0,0 @@
<?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"
tools:context=".songs.SongsFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/song_toolbar"
style="@style/Widget.Toolbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu_songs"
app:title="@string/lbl_all_songs" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/song_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_toolbar"
tools:listitem="@layout/item_song" />
<org.oxycblt.auxio.recycler.FastScrollView
android:id="@+id/song_fast_scroll"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,50 +0,0 @@
<?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">
<merge
tools:layout_height="match_parent"
tools:layout_width="match_parent"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<FrameLayout
android:id="@+id/scroll_thumb"
android:layout_width="@dimen/size_scroll_thumb"
android:layout_height="@dimen/size_scroll_thumb"
android:background="@drawable/ui_circle"
android:backgroundTint="?attr/colorAccent"
android:elevation="@dimen/elevation_small"
android:stateListAnimator="@animator/animator_thumb"
app:layout_constraintEnd_toStartOf="@+id/scroll_indicator_text">
<TextView
android:id="@+id/scroll_thumb_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/inter_semibold"
android:gravity="center"
android:textColor="?android:attr/windowBackground"
android:textSize="@dimen/text_size_medium"
tools:text="A" />
</FrameLayout>
<TextView
android:id="@+id/scroll_indicator_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter_semibold"
android:gravity="center"
android:includeFontPadding="false"
android:lineSpacingExtra="@dimen/spacing_tiny"
android:minWidth="@dimen/width_fast_scroll"
android:paddingTop="@dimen/spacing_tiny"
android:paddingBottom="@dimen/spacing_tiny"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="A\nB\n\C\nD\nE" />
</merge>
</layout>

View file

@ -2,11 +2,17 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search"
android:title="@string/lbl_search"
app:showAsAction="ifRoom" />
<item
android:id="@+id/submenu_sorting"
android:icon="@drawable/ic_sort_none"
android:title="@string/lbl_sort"
app:showAsAction="always">
app:showAsAction="ifRoom">
<menu>
<group android:checkableBehavior="single">
<item
@ -18,4 +24,10 @@
</group>
</menu>
</item>
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"
android:title="@string/set_title"
app:showAsAction="ifRoom"/>
</menu>

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/library_fragment"
android:icon="@drawable/ic_library"
android:title="@string/lbl_library" />
<item
android:id="@+id/songs_fragment"
android:icon="@drawable/ic_song"
android:title="@string/lbl_songs" />
<item
android:id="@+id/search_fragment"
android:icon="@drawable/ic_search"
android:title="@string/lbl_search" />
<item
android:id="@+id/settings_fragment"
android:icon="@drawable/ic_settings"
android:title="@string/set_title" />
</menu>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_shuffle"
android:icon="@drawable/ic_shuffle"
android:title="@string/lbl_shuffle"
app:showAsAction="always" />
</menu>

View file

@ -2,35 +2,8 @@
<navigation 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"
app:startDestination="@id/library_fragment">
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/library_fragment"
android:name="org.oxycblt.auxio.library.LibraryFragment"
android:label="fragment_library"
tools:layout="@layout/fragment_library">
<action
android:id="@+id/action_show_genre"
app:destination="@id/genre_detail_fragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
<fragment
android:id="@+id/artist_detail_fragment"
android:name="org.oxycblt.auxio.detail.ArtistDetailFragment"
@ -100,11 +73,6 @@
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
<fragment
android:id="@+id/songs_fragment"
android:name="org.oxycblt.auxio.songs.SongsFragment"
android:label="fragment_songs"
tools:layout="@layout/fragment_songs" />
<fragment
android:id="@+id/search_fragment"
android:name="org.oxycblt.auxio.search.SearchFragment"
@ -133,8 +101,37 @@
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
<fragment
android:id="@+id/settings_fragment"
android:name="org.oxycblt.auxio.settings.SettingsFragment"
android:label="SettingsFragment"
tools:layout="@layout/fragment_settings" />
android:id="@+id/home_fragment"
android:name="org.oxycblt.auxio.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_show_search"
app:destination="@id/search_fragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_show_genre"
app:destination="@id/genre_detail_fragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_show_album"
app:destination="@id/album_detail_fragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
</navigation>

View file

@ -32,8 +32,8 @@
app:popEnterAnim="@anim/anim_stationary"
app:popExitAnim="@anim/anim_nav_slide_down" />
<action
android:id="@+id/action_show_about"
app:destination="@id/about_fragment"
android:id="@+id/action_show_settings"
app:destination="@id/settings_fragment"
app:enterAnim="@anim/anim_nav_slide_up"
app:exitAnim="@anim/anim_stationary"
app:popEnterAnim="@anim/anim_stationary"
@ -62,4 +62,17 @@
android:name="org.oxycblt.auxio.settings.AboutFragment"
android:label="dialog_about"
tools:layout="@layout/fragment_about" />
<fragment
android:id="@+id/settings_fragment"
android:name="org.oxycblt.auxio.settings.SettingsFragment"
android:label="fragment_settings"
tools:layout="@layout/fragment_settings">
<action
android:id="@+id/action_show_about"
app:destination="@id/about_fragment"
app:enterAnim="@anim/anim_nav_slide_up"
app:exitAnim="@anim/anim_stationary"
app:popEnterAnim="@anim/anim_stationary"
app:popExitAnim="@anim/anim_nav_slide_down" />
</fragment>
</navigation>

View file

@ -6,7 +6,6 @@
-->
<!-- Spacing Namespace | Dimens for padding/margin attributes -->
<dimen name="spacing_tiny">4dp</dimen>
<dimen name="spacing_small">8dp</dimen>
<dimen name="spacing_medium">16dp</dimen>
<dimen name="spacing_mid_large">24dp</dimen>
@ -22,9 +21,6 @@
<dimen name="size_btn_small">48dp</dimen>
<dimen name="size_btn_large">64dp</dimen>
<dimen name="size_small_unb_ripple">20dp</dimen>
<dimen name="size_unb_ripple">24dp</dimen>
<dimen name="size_cover_compact">48dp</dimen>
<dimen name="size_cover_normal">56dp</dimen>
<dimen name="size_cover_huge_land">136dp</dimen>
@ -33,8 +29,8 @@
<dimen name="size_stroke_small">1dp</dimen>
<dimen name="size_stroke_large">2dp</dimen>
<dimen name="size_app_icon">48dp</dimen>
<dimen name="size_scroll_thumb">48dp</dimen>
<dimen name="size_small_unb_ripple">20dp</dimen>
<dimen name="size_unb_ripple">24dp</dimen>
<!-- Text Size Namespace | Text Sizes -->
<dimen name="text_size_small">16sp</dimen>

View file

@ -14,7 +14,7 @@
<!-- Colors -->
<item name="colorSurface">@color/surface</item>
<item name="colorAccent">@color/design_default_color_primary</item>
<item name="colorOnSurface">#FFFFFF</item>
<item name="colorOnSurface">#00000000</item>
<item name="colorPrimary">?attr/colorAccent</item>
<item name="colorSecondary">?attr/colorAccent</item>

View file

@ -7,7 +7,6 @@
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">?android:attr/actionBarSize</item>
<item name="android:background">?attr/colorSurface</item>
<item name="android:elevation">@dimen/elevation_normal</item>
<item name="popupTheme">@style/ThemeOverlay.ToolbarPopup</item>
<item name="titleTextAppearance">@style/TextAppearance.Toolbar.Header</item>
@ -61,6 +60,10 @@
<item name="android:outlineProvider">bounds</item>
</style>
<style name="TextAppearance.TabLayout.Label" parent="@style/TextAppearance.Design.Tab">
<item name="android:fontFamily">@font/inter_semibold</item>
</style>
<!-- VIEWGROUP STYLES -->
<style name="TextAppearance.Toolbar.Header" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
@ -78,6 +81,7 @@
</style>
<!-- TEXTVIEW STYLES -->
<style name="Widget.TextView.Item.Base" parent="Widget.AppCompat.TextView">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>