Add specific playback fragment
Add a Playback Fragment that shows more information about what is currently playing.
This commit is contained in:
parent
59c087d653
commit
0c069dfbac
12 changed files with 175 additions and 13 deletions
|
@ -1,7 +1,6 @@
|
||||||
package org.oxycblt.auxio
|
package org.oxycblt.auxio
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
@ -10,7 +9,7 @@ import org.oxycblt.auxio.theme.accent
|
||||||
// FIXME: Fix bug where fast navigation will break the fade animation and
|
// FIXME: Fix bug where fast navigation will break the fade animation and
|
||||||
// lead to nothing being displayed [Possibly Un-fixable]
|
// lead to nothing being displayed [Possibly Un-fixable]
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity(R.layout.activity_main) {
|
||||||
|
|
||||||
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
||||||
// Apply the theme
|
// Apply the theme
|
||||||
|
@ -18,9 +17,4 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
return super.onCreateView(name, context, attrs)
|
return super.onCreateView(name, context, attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
||||||
import org.oxycblt.auxio.library.LibraryFragment
|
import org.oxycblt.auxio.library.LibraryFragment
|
||||||
import org.oxycblt.auxio.music.MusicViewModel
|
import org.oxycblt.auxio.music.MusicViewModel
|
||||||
|
import org.oxycblt.auxio.playback.PlaybackFragment
|
||||||
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.songs.SongsFragment
|
import org.oxycblt.auxio.songs.SongsFragment
|
||||||
import org.oxycblt.auxio.theme.accent
|
import org.oxycblt.auxio.theme.accent
|
||||||
import org.oxycblt.auxio.theme.getInactiveAlpha
|
import org.oxycblt.auxio.theme.getInactiveAlpha
|
||||||
|
@ -26,8 +28,9 @@ class MainFragment : Fragment() {
|
||||||
MusicViewModel.Factory(requireActivity().application)
|
MusicViewModel.Factory(requireActivity().application)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val shownFragments = listOf(0, 1)
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private val shownFragments = listOf(0, 1)
|
||||||
private val tabIcons = listOf(
|
private val tabIcons = listOf(
|
||||||
R.drawable.ic_library,
|
R.drawable.ic_library,
|
||||||
R.drawable.ic_song
|
R.drawable.ic_song
|
||||||
|
@ -72,8 +75,6 @@ class MainFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
binding.compactPlayback.visibility = View.GONE
|
|
||||||
|
|
||||||
// Set up the selected/deselected colors
|
// Set up the selected/deselected colors
|
||||||
binding.mainTabs.addOnTabSelectedListener(
|
binding.mainTabs.addOnTabSelectedListener(
|
||||||
object : TabLayout.OnTabSelectedListener {
|
object : TabLayout.OnTabSelectedListener {
|
||||||
|
@ -91,6 +92,16 @@ class MainFragment : Fragment() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
|
playbackModel.shouldOpenPlayback.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
PlaybackFragment().show(requireActivity().supportFragmentManager, "TAG_PLAYBACK")
|
||||||
|
|
||||||
|
playbackModel.doneWithOpenPlayback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Log.d(this::class.simpleName, "Fragment Created.")
|
Log.d(this::class.simpleName, "Fragment Created.")
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
|
|
|
@ -85,6 +85,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
||||||
val item = findItem(R.id.action_search)
|
val item = findItem(R.id.action_search)
|
||||||
val searchView = item.actionView as SearchView
|
val searchView = item.actionView as SearchView
|
||||||
|
|
||||||
|
searchView.queryHint = getString(R.string.hint_search_library)
|
||||||
searchView.setOnQueryTextListener(this@LibraryFragment)
|
searchView.setOnQueryTextListener(this@LibraryFragment)
|
||||||
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
||||||
libraryModel.updateSearchFocusStatus(hasFocus)
|
libraryModel.updateSearchFocusStatus(hasFocus)
|
||||||
|
|
|
@ -33,6 +33,10 @@ class CompactPlaybackFragment : Fragment() {
|
||||||
binding.song = musicModel.songs.value!![0]
|
binding.song = musicModel.songs.value!![0]
|
||||||
binding.root.visibility = View.GONE
|
binding.root.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.root.setOnClickListener {
|
||||||
|
playbackModel.openPlayback()
|
||||||
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
// TODO: Add some kind of animation to when this view becomes visible/invisible.
|
// TODO: Add some kind of animation to when this view becomes visible/invisible.
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.oxycblt.auxio.playback
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
|
||||||
|
|
||||||
|
class PlaybackFragment : BottomSheetDialogFragment() {
|
||||||
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
||||||
|
// TODO: Implement nav to artists/albums
|
||||||
|
// TODO: Possibly implement a trackbar with a spectrum shown as well.
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val binding = FragmentPlaybackBinding.inflate(inflater)
|
||||||
|
|
||||||
|
// --- UI SETUP ---
|
||||||
|
|
||||||
|
// Make marquee scroll work
|
||||||
|
binding.songName.isSelected = true
|
||||||
|
binding.songAlbum.isSelected = true
|
||||||
|
binding.songArtist.isSelected = true
|
||||||
|
|
||||||
|
// --- VIEWMODEL SETUP --
|
||||||
|
playbackModel.currentSong.observe(viewLifecycleOwner) {
|
||||||
|
binding.song = it
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,24 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
|
||||||
|
// TODO: Implement media controls
|
||||||
|
// TODO: Add the playback service itself
|
||||||
class PlaybackViewModel : ViewModel() {
|
class PlaybackViewModel : ViewModel() {
|
||||||
private val mCurrentSong = MutableLiveData<Song>()
|
private val mCurrentSong = MutableLiveData<Song>()
|
||||||
val currentSong: LiveData<Song> get() = mCurrentSong
|
val currentSong: LiveData<Song> get() = mCurrentSong
|
||||||
|
|
||||||
|
private val mShouldOpenPlayback = MutableLiveData<Boolean>()
|
||||||
|
val shouldOpenPlayback: LiveData<Boolean> get() = mShouldOpenPlayback
|
||||||
|
|
||||||
fun updateSong(song: Song) {
|
fun updateSong(song: Song) {
|
||||||
mCurrentSong.value = song
|
mCurrentSong.value = song
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openPlayback() {
|
||||||
|
mShouldOpenPlayback.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doneWithOpenPlayback() {
|
||||||
|
mShouldOpenPlayback.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import org.oxycblt.auxio.recycler.ClickListener
|
||||||
|
|
||||||
// Shared ViewHolders for each ViewModel, providing basic information
|
// Shared ViewHolders for each ViewModel, providing basic information
|
||||||
// All new instances should be created with from() instead of direct instantiation.
|
// All new instances should be created with from() instead of direct instantiation.
|
||||||
// TODO: Add indicators to song recycler items when they're being played.
|
|
||||||
|
|
||||||
class GenreViewHolder private constructor(
|
class GenreViewHolder private constructor(
|
||||||
listener: ClickListener<Genre>,
|
listener: ClickListener<Genre>,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.oxycblt.auxio.music.MusicViewModel
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.ClickListener
|
import org.oxycblt.auxio.recycler.ClickListener
|
||||||
import org.oxycblt.auxio.theme.applyDivider
|
import org.oxycblt.auxio.theme.applyDivider
|
||||||
|
|
||||||
class SongsFragment : Fragment() {
|
class SongsFragment : Fragment() {
|
||||||
private val musicModel: MusicViewModel by activityViewModels {
|
private val musicModel: MusicViewModel by activityViewModels {
|
||||||
MusicViewModel.Factory(requireActivity().application)
|
MusicViewModel.Factory(requireActivity().application)
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
|
android:background="@drawable/ripple"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|
98
app/src/main/res/layout/fragment_playback.xml
Normal file
98
app/src/main/res/layout/fragment_playback.xml
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<?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">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="song"
|
||||||
|
type="org.oxycblt.auxio.music.Song" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/background"
|
||||||
|
android:padding="@dimen/padding_medium"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.DayNight">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/album_cover"
|
||||||
|
android:layout_width="@dimen/cover_size_playback"
|
||||||
|
android:layout_height="@dimen/cover_size_playback"
|
||||||
|
android:contentDescription="@{@string/description_album_cover(song.name)}"
|
||||||
|
android:layout_marginBottom="100dp"
|
||||||
|
app:coverArt="@{song}"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/ic_song" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/song_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:fontFamily="@font/inter_semibold"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:text="@{song.name}"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/song_artist"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/album_cover"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:text="Song Name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/song_artist"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:text="@{song.album.artist.name}"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/song_album"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/album_cover"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/song_name"
|
||||||
|
tools:text="Artist Name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/song_album"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@{song.album.name}"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/album_cover"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/album_cover"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/song_artist"
|
||||||
|
tools:text="Album Name" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/song_seek_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:progressBackgroundTint="?android:attr/colorControlNormal"
|
||||||
|
android:progressTint="?android:attr/colorPrimary"
|
||||||
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
|
android:layout_marginBottom="160dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/album_cover" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
|
@ -45,7 +45,7 @@
|
||||||
app:exitAnim="@anim/nav_default_exit_anim"
|
app:exitAnim="@anim/nav_default_exit_anim"
|
||||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||||
app:destination="@id/genreDetailFragment" />
|
app:destination="@id/genre_detail_fragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_return_to_loading"
|
android:id="@+id/action_return_to_loading"
|
||||||
app:destination="@id/loading_fragment"
|
app:destination="@id/loading_fragment"
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
app:destination="@id/artist_detail_fragment" />
|
app:destination="@id/artist_detail_fragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/genreDetailFragment"
|
android:id="@+id/genre_detail_fragment"
|
||||||
android:name="org.oxycblt.auxio.detail.GenreDetailFragment"
|
android:name="org.oxycblt.auxio.detail.GenreDetailFragment"
|
||||||
android:label="GenreDetailFragment"
|
android:label="GenreDetailFragment"
|
||||||
tools:layout="@layout/fragment_genre_detail">
|
tools:layout="@layout/fragment_genre_detail">
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
<dimen name="cover_size_normal">56dp</dimen>
|
<dimen name="cover_size_normal">56dp</dimen>
|
||||||
<dimen name="cover_size_large">68dp</dimen>
|
<dimen name="cover_size_large">68dp</dimen>
|
||||||
<dimen name="cover_size_huge">250dp</dimen>
|
<dimen name="cover_size_huge">250dp</dimen>
|
||||||
|
<dimen name="cover_size_playback">110dp</dimen>
|
||||||
|
|
||||||
<dimen name="track_number_width">32dp</dimen>
|
<dimen name="track_number_width">32dp</dimen>
|
||||||
<dimen name="track_number_text_size_max">20sp</dimen>
|
<dimen name="track_number_text_size_max">20sp</dimen>
|
||||||
|
|
Loading…
Reference in a new issue