Revert to normal navigation structure

Revert to a saner navigation structure for the time being. Nested nav is interesting but I dont want to deal with the bugs at this moment.
This commit is contained in:
OxygenCobalt 2020-09-11 10:23:32 -06:00
parent 5814907798
commit 02e803746b
11 changed files with 384 additions and 223 deletions

View file

@ -3,49 +3,15 @@ package org.oxycblt.auxio
import android.content.Context
import android.os.Bundle
import android.util.AttributeSet
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelProvider
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.databinding.ActivityMainBinding
import org.oxycblt.auxio.library.LibraryFragment
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.processing.MusicLoaderResponse
import org.oxycblt.auxio.songs.SongsFragment
import org.oxycblt.auxio.theme.accent
import org.oxycblt.auxio.theme.getInactiveAlpha
import org.oxycblt.auxio.theme.getTransparentAccent
import org.oxycblt.auxio.theme.toColor
class MainActivity : AppCompatActivity() {
private val shownFragments = listOf(0, 1)
private val libraryFragment: LibraryFragment by lazy { LibraryFragment() }
private val songsFragment: SongsFragment by lazy { SongsFragment() }
private val tabIcons = listOf(
R.drawable.ic_library,
R.drawable.ic_song
)
private lateinit var binding: ActivityMainBinding
private val musicModel: MusicViewModel by lazy {
ViewModelProvider(
this, MusicViewModel.Factory(application)
).get(MusicViewModel::class.java)
}
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
// Debug placeholder, ignore
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
// Apply the theme
@ -56,92 +22,6 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this, R.layout.activity_main
)
binding.lifecycleOwner = this
val adapter = PagerAdapter(this)
binding.viewPager.adapter = adapter
val colorActive = accent.first.toColor(baseContext)
val colorInactive = getTransparentAccent(
baseContext,
accent.first,
getInactiveAlpha(accent.first)
)
// Link the ViewPager & Tab View
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.icon = ContextCompat.getDrawable(baseContext, tabIcons[position])
// Set the icon tint to deselected if its not the default tab
if (position > 0) {
tab.icon?.setTint(colorInactive)
}
// Init the fragment
fragmentAt(position)
}.attach()
// Set up the selected/deselected colors
binding.tabs.addOnTabSelectedListener(
object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
tab.icon?.setTint(colorActive)
}
override fun onTabUnselected(tab: TabLayout.Tab) {
tab.icon?.setTint(colorInactive)
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
}
)
musicModel.response.observe(
this,
{
if (it == MusicLoaderResponse.DONE) {
binding.loadingFragment.visibility = View.GONE
binding.viewPager.visibility = View.VISIBLE
}
}
)
Log.d(this::class.simpleName, "Activity Created.")
}
private fun fragmentAt(position: Int): Fragment {
return when (position) {
0 -> libraryFragment
1 -> songsFragment
else -> libraryFragment
}
}
private inner class PagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
override fun getItemCount(): Int = shownFragments.size
override fun createFragment(position: Int): Fragment {
Log.d(this::class.simpleName, "Switching to fragment $position.")
if (shownFragments.contains(position)) {
return fragmentAt(position)
}
// Not sure how this would happen but it might
Log.e(
this::class.simpleName,
"Attempted to index a fragment that shouldn't be shown. Returning libraryFragment."
)
return libraryFragment
}
setContentView(R.layout.activity_main)
}
}

View file

@ -0,0 +1,122 @@
package org.oxycblt.auxio
import android.content.Context
import android.os.Bundle
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.library.LibraryFragment
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.processing.MusicLoaderResponse
import org.oxycblt.auxio.songs.SongsFragment
import org.oxycblt.auxio.theme.accent
import org.oxycblt.auxio.theme.getInactiveAlpha
import org.oxycblt.auxio.theme.getTransparentAccent
import org.oxycblt.auxio.theme.toColor
class MainFragment : Fragment() {
private val shownFragments = listOf(0, 1)
private val libraryFragment: LibraryFragment by lazy { LibraryFragment() }
private val songsFragment: SongsFragment by lazy { SongsFragment() }
private val tabIcons = listOf(
R.drawable.ic_library,
R.drawable.ic_song
)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentMainBinding.inflate(inflater)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewPager.adapter = PagerAdapter()
val colorActive = accent.first.toColor(requireContext())
val colorInactive = getTransparentAccent(
requireContext(),
accent.first,
getInactiveAlpha(accent.first)
)
// Link the ViewPager & Tab View
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.icon = ContextCompat.getDrawable(requireContext(), tabIcons[position])
// Set the icon tint to deselected if its not the default tab
if (position > 0) {
tab.icon?.setTint(colorInactive)
}
// Init the fragment
fragmentAt(position)
}.attach()
// Set up the selected/deselected colors
binding.tabs.addOnTabSelectedListener(
object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
tab.icon?.setTint(colorActive)
}
override fun onTabUnselected(tab: TabLayout.Tab) {
tab.icon?.setTint(colorInactive)
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
}
)
Log.d(this::class.simpleName, "Fragment Created.")
return binding.root
}
private fun fragmentAt(position: Int): Fragment {
return when (position) {
0 -> libraryFragment
1 -> songsFragment
else -> libraryFragment
}
}
private inner class PagerAdapter : FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
override fun getItemCount(): Int = shownFragments.size
override fun createFragment(position: Int): Fragment {
Log.d(this::class.simpleName, "Switching to fragment $position.")
if (shownFragments.contains(position)) {
return fragmentAt(position)
}
// Not sure how this would happen but it might
Log.e(
this::class.simpleName,
"Attempted to index a fragment that shouldn't be shown. Returning libraryFragment."
)
return libraryFragment
}
}
}

View file

@ -19,9 +19,7 @@ class ArtistDetailFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentArtistDetailBinding>(
inflater, R.layout.fragment_artist_detail, container, false
)
val binding = FragmentArtistDetailBinding.inflate(inflater)
// I honestly don't want to turn of the any data classes into a parcelables due to how
// many lists they store, so just pick up the artist id and find it from musicModel.

View file

@ -2,51 +2,52 @@ package org.oxycblt.auxio.library
import android.os.Bundle
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.navigation.fragment.NavHostFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.MainFragment
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLibraryBinding
import org.oxycblt.auxio.library.adapters.ArtistAdapter
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.models.Artist
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.applyDivider
class LibraryFragment : NavHostFragment() {
class LibraryFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
private val musicModel: MusicViewModel by activityViewModels()
requireActivity().onBackPressedDispatcher.addCallback(
this, callback
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentLibraryBinding.inflate(inflater)
binding.libraryRecycler.adapter = ArtistAdapter(
musicModel.artists.value!!,
ClickListener { navToArtist(it) }
)
navController.setGraph(R.navigation.nav_library)
binding.libraryRecycler.applyDivider()
binding.libraryRecycler.setHasFixedSize(true)
Log.d(this::class.simpleName, "Fragment created.")
return binding.root
}
override fun onResume() {
super.onResume()
private fun navToArtist(artist: Artist) {
// Don't navigate to a fragment multiple times if multiple items are accepted.
callback.isEnabled = true
}
override fun onPause() {
super.onPause()
callback.isEnabled = false
}
val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
// If at the root of the navigation, perform onBackPressed from the main activity.
if (navController.currentDestination?.id == navController.graph.startDestination) {
// Disable the callback as it will get caught in an infinite loop otherwise.
isEnabled = false
requireActivity().onBackPressed()
isEnabled = true
} else {
navController.navigateUp()
}
}
findNavController().navigate(
MainFragmentDirections.actionShowArtist(artist.id)
)
}
}

View file

@ -13,6 +13,7 @@ import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLoadingBinding
import org.oxycblt.auxio.music.MusicViewModel
@ -20,6 +21,8 @@ import org.oxycblt.auxio.music.processing.MusicLoaderResponse
class LoadingFragment : Fragment(R.layout.fragment_loading) {
// LoadingFragment is [hopefully] going to be the first one to have to create musicModel,
// so pass a factory instance so that the model has access to the application resources.
private val musicModel: MusicViewModel by activityViewModels {
MusicViewModel.Factory(requireActivity().application)
}
@ -32,9 +35,7 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater, R.layout.fragment_loading, container, false
)
binding = FragmentLoadingBinding.inflate(inflater)
binding.lifecycleOwner = this
binding.musicModel = musicModel
@ -87,6 +88,10 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
}
// Check for two things:
// - If Auxio needs to show the rationale for getting the READ_EXTERNAL_STORAGE permission.
// - If Auxio straight up doesn't have the READ_EXTERNAL_STORAGE permission.
@ -98,13 +103,16 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
) == PackageManager.PERMISSION_DENIED
}
private fun onMusicLoadResponse(repoResponse: MusicLoaderResponse?) {
// Don't run this if the value is null, Which is what the value changes to after
// this is run.
repoResponse?.let { response ->
binding.loadingBar.visibility = View.GONE
private fun onMusicLoadResponse(response: MusicLoaderResponse?) {
binding.loadingBar.visibility = View.GONE
if (response != MusicLoaderResponse.DONE) {
if (response == MusicLoaderResponse.DONE) {
findNavController().navigate(
LoadingFragmentDirections.actionToMain()
)
}
else {
binding.let { binding ->
binding.errorText.text =
if (response == MusicLoaderResponse.NO_MUSIC)
getString(R.string.error_no_music)
@ -126,9 +134,9 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
// along with a GRANT button
binding.loadingBar.visibility = View.GONE
binding.errorText.visibility = View.VISIBLE
binding.statusIcon.visibility = View.VISIBLE
binding.grantButton.visibility = View.VISIBLE
binding.errorText.visibility = View.VISIBLE
binding.errorText.text = getString(R.string.error_no_perms)
}

View file

@ -1,53 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_main"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="musicModel"
type="org.oxycblt.auxio.music.MusicViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/tabs"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/loading_fragment"
android:name="org.oxycblt.auxio.loading.LoadingFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/tabs"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_menu_size"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:layout_constraintBottom_toBottomOf="parent"
app:tabGravity="fill"
app:tabIconTint="?android:attr/colorPrimary"
app:tabIconTintMode="src_in"
app:tabIndicator="@drawable/indicator"
app:tabIndicatorColor="?android:attr/colorPrimary"
app:tabMode="fixed"
app:tabRippleColor="@color/selection_color" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
xmlns:android="http://schemas.android.com/apk/res/android" />

View file

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<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">
<data>
@ -14,9 +16,20 @@
android:orientation="vertical"
android:animateLayoutChanges="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold"
app:title="@string/title_library_fragment"
tools:titleTextColor="@color/blue" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{artist.name}" />
</LinearLayout>
</layout>

View file

@ -0,0 +1,30 @@
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:animateLayoutChanges="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold"
app:title="@string/title_library_fragment"
tools:titleTextColor="@color/blue" />
<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"
tools:listitem="@layout/item_album" />
</LinearLayout>
</layout>

View file

@ -0,0 +1,35 @@
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_menu_size"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:layout_constraintBottom_toBottomOf="parent"
app:tabGravity="fill"
app:tabIconTint="?android:attr/colorPrimary"
app:tabIconTintMode="src_in"
app:tabIndicator="@drawable/indicator"
app:tabIndicatorColor="?android:attr/colorPrimary"
app:tabMode="fixed"
app:tabRippleColor="@color/selection_color" />
</LinearLayout>
</layout>

View file

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="song"
type="org.oxycblt.auxio.music.models.Song" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ripple"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/padding_medium">
<ImageView
android:id="@+id/cover"
android:layout_width="@dimen/cover_size_compact"
android:layout_height="@dimen/cover_size_compact"
app:coverArt="@{song}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/song_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_medium"
android:ellipsize="end"
android:maxLines="1"
android:text="@{song.name}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/song_info"
app:layout_constraintEnd_toStartOf="@+id/duration"
app:layout_constraintStart_toEndOf="@+id/cover"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Song Name" />
<TextView
android:id="@+id/song_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_medium"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:text="@{@string/format_song_info(song.album.artist.name, song.album.name)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/duration"
app:layout_constraintStart_toEndOf="@+id/cover"
app:layout_constraintTop_toBottomOf="@+id/song_name"
tools:text="Artist / Album" />
<TextView
android:id="@+id/duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@{song.formattedDuration}"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorTertiary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="16:16" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-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"
android:id="@+id/nav_main"
app:startDestination="@id/loading_fragment">
<fragment
android:id="@+id/loading_fragment"
android:name="org.oxycblt.auxio.loading.LoadingFragment"
android:label="LoadingFragment"
tools:layout="@layout/fragment_loading">
<action
android:id="@+id/action_to_main"
app:destination="@id/main_fragment"
app:popUpTo="@id/loading_fragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/main_fragment"
android:name="org.oxycblt.auxio.MainFragment"
android:label="MainFragment"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_show_artist"
app:destination="@id/artist_detail_fragment" />
</fragment>
<fragment
android:id="@+id/artist_detail_fragment"
android:name="org.oxycblt.auxio.detail.ArtistDetailFragment"
android:label="ArtistDetailFragment"
tools:layout="@layout/fragment_artist_detail">
<argument
android:name="artistId"
app:argType="long" />
</fragment>
</navigation>