ui: remove memberBinding [#80]
Remove all usages of memberBinding from the app. For some reason, certain devices running Android 10 and lower will have a lifecycle race condition whenever the theme is mis-matched. This ends up resulting in an invalid state whenever memberBinder was used. Since we can't replace memberBinder with a better solution, just dumpster the whole thing. This platform is so god damn broken, jesus christ. Resolves #80.
This commit is contained in:
parent
fc4c7714a2
commit
c5be39774a
13 changed files with 66 additions and 132 deletions
|
@ -6,6 +6,9 @@
|
||||||
- Rounded images are more nuanced
|
- Rounded images are more nuanced
|
||||||
- Shuffle and Repeat mode buttons now have more contrast when they are turned on
|
- Shuffle and Repeat mode buttons now have more contrast when they are turned on
|
||||||
|
|
||||||
|
#### What's Fixed
|
||||||
|
- Fixed crash on certain devices running Android 10 and lower when a differing theme from the system theme was used.
|
||||||
|
|
||||||
#### What's Changed
|
#### What's Changed
|
||||||
- All cover art is now cropped to a 1:1 aspect ratio
|
- All cover art is now cropped to a 1:1 aspect ratio
|
||||||
|
|
||||||
|
@ -14,6 +17,7 @@
|
||||||
- Switches now have a disabled state
|
- Switches now have a disabled state
|
||||||
- Reworked dynamic color usage
|
- Reworked dynamic color usage
|
||||||
- Reworked logging
|
- Reworked logging
|
||||||
|
- Upgrade ExoPlayer to v2.17.0 [Eliminates custom fork]
|
||||||
|
|
||||||
## v2.2.1
|
## v2.2.1
|
||||||
#### What's Improved
|
#### What's Improved
|
||||||
|
|
|
@ -57,6 +57,7 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
): View {
|
): View {
|
||||||
detailModel.setAlbum(args.albumId)
|
detailModel.setAlbum(args.albumId)
|
||||||
|
|
||||||
|
val binding = FragmentDetailBinding.inflate(layoutInflater)
|
||||||
val detailAdapter = AlbumDetailAdapter(
|
val detailAdapter = AlbumDetailAdapter(
|
||||||
playbackModel, detailModel,
|
playbackModel, detailModel,
|
||||||
doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
|
doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
|
||||||
|
@ -67,7 +68,7 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
setupToolbar(detailModel.curAlbum.value!!, R.menu.menu_album_detail) { itemId ->
|
setupToolbar(detailModel.curAlbum.value!!, binding, R.menu.menu_album_detail) { itemId ->
|
||||||
when (itemId) {
|
when (itemId) {
|
||||||
R.id.action_play_next -> {
|
R.id.action_play_next -> {
|
||||||
playbackModel.playNext(detailModel.curAlbum.value!!)
|
playbackModel.playNext(detailModel.curAlbum.value!!)
|
||||||
|
@ -85,7 +86,7 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupRecycler(detailAdapter) { pos ->
|
setupRecycler(binding, detailAdapter) { pos ->
|
||||||
val item = detailAdapter.currentList[pos]
|
val item = detailAdapter.currentList[pos]
|
||||||
item is Header || item is ActionHeader || item is Album
|
item is Header || item is ActionHeader || item is Album
|
||||||
}
|
}
|
||||||
|
@ -113,7 +114,7 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
is Song -> {
|
is Song -> {
|
||||||
if (detailModel.curAlbum.value!!.id == item.album.id) {
|
if (detailModel.curAlbum.value!!.id == item.album.id) {
|
||||||
logD("Navigating to a song in this album")
|
logD("Navigating to a song in this album")
|
||||||
scrollToItem(item.id, detailAdapter)
|
scrollToItem(item.id, binding, detailAdapter)
|
||||||
detailModel.finishNavToItem()
|
detailModel.finishNavToItem()
|
||||||
} else {
|
} else {
|
||||||
logD("Navigating to another album")
|
logD("Navigating to another album")
|
||||||
|
@ -185,7 +186,11 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
/**
|
/**
|
||||||
* Scroll to an song using its [id].
|
* Scroll to an song using its [id].
|
||||||
*/
|
*/
|
||||||
private fun scrollToItem(id: Long, adapter: AlbumDetailAdapter) {
|
private fun scrollToItem(
|
||||||
|
id: Long,
|
||||||
|
binding: FragmentDetailBinding,
|
||||||
|
adapter: AlbumDetailAdapter
|
||||||
|
) {
|
||||||
// Calculate where the item for the currently played song is
|
// Calculate where the item for the currently played song is
|
||||||
val pos = adapter.currentList.indexOfFirst { it.id == id && it is Song }
|
val pos = adapter.currentList.indexOfFirst { it.id == id && it is Song }
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
||||||
import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter
|
import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter
|
||||||
import org.oxycblt.auxio.music.ActionHeader
|
import org.oxycblt.auxio.music.ActionHeader
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
|
@ -51,6 +52,7 @@ class ArtistDetailFragment : DetailFragment() {
|
||||||
): View {
|
): View {
|
||||||
detailModel.setArtist(args.artistId)
|
detailModel.setArtist(args.artistId)
|
||||||
|
|
||||||
|
val binding = FragmentDetailBinding.inflate(layoutInflater)
|
||||||
val detailAdapter = ArtistDetailAdapter(
|
val detailAdapter = ArtistDetailAdapter(
|
||||||
playbackModel,
|
playbackModel,
|
||||||
doOnClick = { data ->
|
doOnClick = { data ->
|
||||||
|
@ -74,8 +76,8 @@ class ArtistDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
setupToolbar(detailModel.curArtist.value!!)
|
setupToolbar(detailModel.curArtist.value!!, binding)
|
||||||
setupRecycler(detailAdapter) { pos ->
|
setupRecycler(binding, detailAdapter) { pos ->
|
||||||
// If the item is an ActionHeader we need to also make the item full-width
|
// If the item is an ActionHeader we need to also make the item full-width
|
||||||
val item = detailAdapter.currentList[pos]
|
val item = detailAdapter.currentList[pos]
|
||||||
item is Header || item is ActionHeader || item is Artist
|
item is Header || item is ActionHeader || item is Artist
|
||||||
|
|
|
@ -23,13 +23,13 @@ import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.navigation.Navigation.findNavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
||||||
import org.oxycblt.auxio.music.MusicParent
|
import org.oxycblt.auxio.music.MusicParent
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.memberBinding
|
|
||||||
import org.oxycblt.auxio.util.applySpans
|
import org.oxycblt.auxio.util.applySpans
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@ import org.oxycblt.auxio.util.logD
|
||||||
abstract class DetailFragment : Fragment() {
|
abstract class DetailFragment : Fragment() {
|
||||||
protected val detailModel: DetailViewModel by activityViewModels()
|
protected val detailModel: DetailViewModel by activityViewModels()
|
||||||
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
protected val binding by memberBinding(FragmentDetailBinding::inflate)
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
@ -61,6 +60,7 @@ abstract class DetailFragment : Fragment() {
|
||||||
*/
|
*/
|
||||||
protected fun setupToolbar(
|
protected fun setupToolbar(
|
||||||
data: MusicParent,
|
data: MusicParent,
|
||||||
|
binding: FragmentDetailBinding,
|
||||||
@MenuRes menuId: Int = -1,
|
@MenuRes menuId: Int = -1,
|
||||||
onMenuClick: ((itemId: Int) -> Boolean)? = null
|
onMenuClick: ((itemId: Int) -> Boolean)? = null
|
||||||
) {
|
) {
|
||||||
|
@ -87,6 +87,7 @@ abstract class DetailFragment : Fragment() {
|
||||||
* Shortcut method for recyclerview setup
|
* Shortcut method for recyclerview setup
|
||||||
*/
|
*/
|
||||||
protected fun setupRecycler(
|
protected fun setupRecycler(
|
||||||
|
binding: FragmentDetailBinding,
|
||||||
detailAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>,
|
detailAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>,
|
||||||
gridLookup: (Int) -> Boolean
|
gridLookup: (Int) -> Boolean
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
||||||
import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter
|
import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter
|
||||||
import org.oxycblt.auxio.music.ActionHeader
|
import org.oxycblt.auxio.music.ActionHeader
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
|
@ -51,6 +52,7 @@ class GenreDetailFragment : DetailFragment() {
|
||||||
): View {
|
): View {
|
||||||
detailModel.setGenre(args.genreId)
|
detailModel.setGenre(args.genreId)
|
||||||
|
|
||||||
|
val binding = FragmentDetailBinding.inflate(inflater)
|
||||||
val detailAdapter = GenreDetailAdapter(
|
val detailAdapter = GenreDetailAdapter(
|
||||||
playbackModel,
|
playbackModel,
|
||||||
doOnClick = { song ->
|
doOnClick = { song ->
|
||||||
|
@ -65,8 +67,8 @@ class GenreDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
setupToolbar(detailModel.curGenre.value!!)
|
setupToolbar(detailModel.curGenre.value!!, binding)
|
||||||
setupRecycler(detailAdapter) { pos ->
|
setupRecycler(binding, detailAdapter) { pos ->
|
||||||
val item = detailAdapter.currentList[pos]
|
val item = detailAdapter.currentList[pos]
|
||||||
item is Header || item is ActionHeader || item is Genre
|
item is Header || item is ActionHeader || item is Genre
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.home.HomeFragmentDirections
|
import org.oxycblt.auxio.home.HomeFragmentDirections
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.toDate
|
import org.oxycblt.auxio.music.toDate
|
||||||
|
@ -43,6 +44,10 @@ class AlbumListFragment : HomeListFragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
|
val binding = FragmentHomeListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
// / --- UI SETUP ---
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
val adapter = AlbumAdapter(
|
val adapter = AlbumAdapter(
|
||||||
|
@ -54,7 +59,7 @@ class AlbumListFragment : HomeListFragment() {
|
||||||
::newMenu
|
::newMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
setupRecycler(R.id.home_album_list, adapter, homeModel.albums)
|
setupRecycler(R.id.home_album_list, binding, adapter, homeModel.albums)
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.home.HomeFragmentDirections
|
import org.oxycblt.auxio.home.HomeFragmentDirections
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.ui.ArtistViewHolder
|
import org.oxycblt.auxio.ui.ArtistViewHolder
|
||||||
|
@ -40,6 +41,10 @@ class ArtistListFragment : HomeListFragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
|
val binding = FragmentHomeListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
// / --- UI SETUP ---
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
val adapter = ArtistAdapter(
|
val adapter = ArtistAdapter(
|
||||||
|
@ -51,7 +56,7 @@ class ArtistListFragment : HomeListFragment() {
|
||||||
::newMenu
|
::newMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
setupRecycler(R.id.home_artist_list, adapter, homeModel.artists)
|
setupRecycler(R.id.home_artist_list, binding, adapter, homeModel.artists)
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.home.HomeFragmentDirections
|
import org.oxycblt.auxio.home.HomeFragmentDirections
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.ui.GenreViewHolder
|
import org.oxycblt.auxio.ui.GenreViewHolder
|
||||||
|
@ -40,6 +41,10 @@ class GenreListFragment : HomeListFragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
|
val binding = FragmentHomeListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
// / --- UI SETUP ---
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
val adapter = GenreAdapter(
|
val adapter = GenreAdapter(
|
||||||
|
@ -51,7 +56,7 @@ class GenreListFragment : HomeListFragment() {
|
||||||
::newMenu
|
::newMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
setupRecycler(R.id.home_genre_list, adapter, homeModel.genres)
|
setupRecycler(R.id.home_genre_list, binding, adapter, homeModel.genres)
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.home.HomeViewModel
|
import org.oxycblt.auxio.home.HomeViewModel
|
||||||
import org.oxycblt.auxio.music.Item
|
import org.oxycblt.auxio.music.Item
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.memberBinding
|
|
||||||
import org.oxycblt.auxio.util.applySpans
|
import org.oxycblt.auxio.util.applySpans
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,10 +35,6 @@ import org.oxycblt.auxio.util.applySpans
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
abstract class HomeListFragment : Fragment() {
|
abstract class HomeListFragment : Fragment() {
|
||||||
protected val binding: FragmentHomeListBinding by memberBinding(
|
|
||||||
FragmentHomeListBinding::inflate
|
|
||||||
)
|
|
||||||
|
|
||||||
protected val homeModel: HomeViewModel by activityViewModels()
|
protected val homeModel: HomeViewModel by activityViewModels()
|
||||||
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
||||||
|
@ -50,6 +45,7 @@ abstract class HomeListFragment : Fragment() {
|
||||||
|
|
||||||
protected fun <T : Item, VH : RecyclerView.ViewHolder> setupRecycler(
|
protected fun <T : Item, VH : RecyclerView.ViewHolder> setupRecycler(
|
||||||
@IdRes uniqueId: Int,
|
@IdRes uniqueId: Int,
|
||||||
|
binding: FragmentHomeListBinding,
|
||||||
homeAdapter: HomeAdapter<T, VH>,
|
homeAdapter: HomeAdapter<T, VH>,
|
||||||
homeData: LiveData<List<T>>,
|
homeData: LiveData<List<T>>,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.toDate
|
import org.oxycblt.auxio.music.toDate
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
|
@ -41,6 +42,10 @@ class SongListFragment : HomeListFragment() {
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
|
val binding = FragmentHomeListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
// / --- UI SETUP ---
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
val adapter = SongsAdapter(
|
val adapter = SongsAdapter(
|
||||||
|
@ -50,7 +55,7 @@ class SongListFragment : HomeListFragment() {
|
||||||
::newMenu
|
::newMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
setupRecycler(R.id.home_song_list, adapter, homeModel.songs)
|
setupRecycler(R.id.home_song_list, binding, adapter, homeModel.songs)
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
|
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
|
||||||
import org.oxycblt.auxio.detail.DetailViewModel
|
import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
import org.oxycblt.auxio.playback.state.LoopMode
|
import org.oxycblt.auxio.playback.state.LoopMode
|
||||||
import org.oxycblt.auxio.ui.memberBinding
|
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
||||||
|
@ -44,17 +43,19 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
class PlaybackFragment : Fragment() {
|
class PlaybackFragment : Fragment() {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
private val detailModel: DetailViewModel by activityViewModels()
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
private val binding by memberBinding(FragmentPlaybackBinding::inflate) {
|
private var mLastBinding: FragmentPlaybackBinding? = null
|
||||||
playbackSong.isSelected = false // Clear marquee to prevent a memory leak
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
|
val binding = FragmentPlaybackBinding.inflate(layoutInflater)
|
||||||
val queueItem: MenuItem
|
val queueItem: MenuItem
|
||||||
|
|
||||||
|
// See onDestroyView for why we do this
|
||||||
|
mLastBinding = binding
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
@ -92,6 +93,8 @@ class PlaybackFragment : Fragment() {
|
||||||
// Make marquee of song title work
|
// Make marquee of song title work
|
||||||
binding.playbackSong.isSelected = true
|
binding.playbackSong.isSelected = true
|
||||||
binding.playbackSeekBar.onConfirmListener = playbackModel::setPosition
|
binding.playbackSeekBar.onConfirmListener = playbackModel::setPosition
|
||||||
|
|
||||||
|
// Abuse the play/pause FAB (see style definition for more info)
|
||||||
binding.playbackPlayPause.post {
|
binding.playbackPlayPause.post {
|
||||||
binding.playbackPlayPause.stateListAnimator = null
|
binding.playbackPlayPause.stateListAnimator = null
|
||||||
}
|
}
|
||||||
|
@ -156,6 +159,15 @@ class PlaybackFragment : Fragment() {
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
|
||||||
|
// playbackSong will leak if we don't disable marquee, keep the binding around
|
||||||
|
// so that we can turn it off when we destroy the view.
|
||||||
|
mLastBinding?.playbackSong?.isSelected = false
|
||||||
|
mLastBinding = null
|
||||||
|
}
|
||||||
|
|
||||||
private fun navigateUp() {
|
private fun navigateUp() {
|
||||||
// This is a dumb and fragile hack but this fragment isn't part of the navigation stack
|
// This is a dumb and fragile hack but this fragment isn't part of the navigation stack
|
||||||
// so we can't really do much
|
// so we can't really do much
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021 Auxio Project
|
|
||||||
* MemberBinder.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.ui
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.databinding.ViewDataBinding
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
|
||||||
import androidx.lifecycle.LifecycleObserver
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import org.oxycblt.auxio.util.assertMainThread
|
|
||||||
import org.oxycblt.auxio.util.inflater
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A delegate that creates a binding that can be used as a member variable without nullability or
|
|
||||||
* memory leaks.
|
|
||||||
* @param inflate The ViewBinding inflation method that should be used
|
|
||||||
* @param onDestroy What to do when the binding is destroyed
|
|
||||||
*/
|
|
||||||
fun <T : ViewDataBinding> Fragment.memberBinding(
|
|
||||||
inflate: (LayoutInflater) -> T,
|
|
||||||
onDestroy: T.() -> Unit = {}
|
|
||||||
) = MemberBinder(this, inflate, onDestroy)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The delegate for the [memberBinding] shortcut function.
|
|
||||||
* Adapted from KAHelpers (https://github.com/FunkyMuse/KAHelpers/tree/master/viewbinding)
|
|
||||||
* @author OxygenCobalt
|
|
||||||
*/
|
|
||||||
class MemberBinder<T : ViewDataBinding>(
|
|
||||||
private val fragment: Fragment,
|
|
||||||
private val inflate: (LayoutInflater) -> T,
|
|
||||||
private val onDestroy: T.() -> Unit
|
|
||||||
) : ReadOnlyProperty<Fragment, T>, LifecycleObserver, LifecycleEventObserver {
|
|
||||||
private var fragmentBinding: T? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
fragment.observeOwnerThroughCreation {
|
|
||||||
lifecycle.addObserver(this@MemberBinder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
|
|
||||||
assertMainThread()
|
|
||||||
|
|
||||||
val binding = fragmentBinding
|
|
||||||
|
|
||||||
// If the fragment is already initialized, then just return that.
|
|
||||||
if (binding != null) {
|
|
||||||
return binding
|
|
||||||
}
|
|
||||||
|
|
||||||
val lifecycle = fragment.viewLifecycleOwner.lifecycle
|
|
||||||
|
|
||||||
check(lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
|
|
||||||
"Fragment views are destroyed"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise create the binding and return that.
|
|
||||||
return inflate(thisRef.requireContext().inflater).also {
|
|
||||||
fragmentBinding = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
|
||||||
if (event == Lifecycle.Event.ON_DESTROY) {
|
|
||||||
fragmentBinding?.onDestroy()
|
|
||||||
fragmentBinding = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun Fragment.observeOwnerThroughCreation(
|
|
||||||
crossinline viewOwner: LifecycleOwner.() -> Unit
|
|
||||||
) {
|
|
||||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
|
||||||
super.onCreate(owner)
|
|
||||||
|
|
||||||
viewLifecycleOwnerLiveData.observe(this@observeOwnerThroughCreation) {
|
|
||||||
it.viewOwner()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -53,9 +53,7 @@ is separated into three phases:
|
||||||
- Set up ViewModel instances and LiveData observers
|
- Set up ViewModel instances and LiveData observers
|
||||||
|
|
||||||
`findViewById` is to **only** be used when interfacing with non-Auxio views. Otherwise, view-binding should be
|
`findViewById` is to **only** be used when interfacing with non-Auxio views. Otherwise, view-binding should be
|
||||||
used in all cases. If one needs to keep track of a view-binding outside of `onCreateView`, then one can declare
|
used in all cases. Avoid usages of databinding outside of the `onCreateView` step unless absolutely necessary.
|
||||||
a binding `by memberBinding(BindingClass::inflate)` in order to have a binding that properly disposes itself
|
|
||||||
on lifecycle events.
|
|
||||||
|
|
||||||
At times it may be more appropriate to use a `View` instead of a full blown fragment. This is okay as long as
|
At times it may be more appropriate to use a `View` instead of a full blown fragment. This is okay as long as
|
||||||
view-binding is still used.
|
view-binding is still used.
|
||||||
|
@ -290,7 +288,6 @@ Shared views and view configuration models. This contains:
|
||||||
- Customized views such as `EdgeAppBarLayout` and `EdgeRecyclerView`, which add some extra functionality not provided by default
|
- Customized views such as `EdgeAppBarLayout` and `EdgeRecyclerView`, which add some extra functionality not provided by default
|
||||||
- Configuration models like `DisplayMode` and `Sort`, which are used in many places but aren't tied to a specific feature.
|
- Configuration models like `DisplayMode` and `Sort`, which are used in many places but aren't tied to a specific feature.
|
||||||
- `newMenu` and `ActionMenu`, which automates menu creation for most data types
|
- `newMenu` and `ActionMenu`, which automates menu creation for most data types
|
||||||
- `memberBinding` and `MemberBinder`, which allows for ViewBindings to be used as a member variable without memory leaks or nullability issues.
|
|
||||||
|
|
||||||
#### `.util`
|
#### `.util`
|
||||||
Shared utilities. This is primarily for QoL when developing Auxio. Documentation is provided on each method.
|
Shared utilities. This is primarily for QoL when developing Auxio. Documentation is provided on each method.
|
||||||
|
|
Loading…
Reference in a new issue