diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0dd89ff5aca500f8de1f37063ff2844912b06b13..0b7d32bd08fccfd5963e6d16dfec54d92ed59a7e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -7,6 +7,7 @@ plugins {
   id("kotlin-android")
   id("androidx.navigation.safeargs.kotlin")
   id("kotlin-parcelize")
+  id("kotlin-kapt")
 
   id("org.jlleitschuh.gradle.ktlint") version "11.2.0"
   id("com.gladed.androidgitversion") version "0.4.14"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 62f7deb597815e258a147a7679a1a54c50835cb9..9bfd708cfac8267d544607fb306676897c38a824 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -22,13 +22,11 @@
             android:name=".activities.SplashActivity"
             android:launchMode="singleInstance"
             android:noHistory="true"
-            android:screenOrientation="portrait"
             android:exported="true">
 
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.intent.action.VIEW" />
-
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
 
@@ -40,9 +38,7 @@
             android:launchMode="singleInstance"
             android:screenOrientation="portrait" />
 
-        <activity
-            android:name=".activities.MainActivity"
-            android:screenOrientation="portrait" />
+        <activity android:name=".activities.MainActivity" />
 
         <activity
             android:name=".activities.DownloadsActivity"
diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt
index d8c916f476f357fa715f6ec1149ca163af44dd33..dbe57fbf42dd96ae6d6af4e838cb391815c61a94 100644
--- a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt
@@ -34,6 +34,7 @@ import audio.funkwhale.ffa.databinding.ActivityMainBinding
 import audio.funkwhale.ffa.fragments.AddToPlaylistDialog
 import audio.funkwhale.ffa.fragments.BrowseFragmentDirections
 import audio.funkwhale.ffa.fragments.LandscapeQueueFragment
+import audio.funkwhale.ffa.fragments.NowPlayingFragment
 import audio.funkwhale.ffa.fragments.QueueFragment
 import audio.funkwhale.ffa.fragments.TrackInfoDetailsFragment
 import audio.funkwhale.ffa.model.Track
@@ -67,6 +68,9 @@ import com.github.kittinunf.fuel.Fuel
 import com.github.kittinunf.fuel.coroutines.awaitStringResponse
 import com.google.android.exoplayer2.Player
 import com.google.android.exoplayer2.offline.DownloadService
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
+import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
 import com.google.gson.Gson
 import com.preference.PowerPreference
 import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
@@ -82,7 +86,6 @@ class MainActivity : AppCompatActivity() {
     LOGOUT(1001)
   }
 
-  private val favoriteRepository = FavoritesRepository(this)
   private val favoritedRepository = FavoritedRepository(this)
   private var menu: Menu? = null
 
@@ -104,8 +107,8 @@ class MainActivity : AppCompatActivity() {
     setSupportActionBar(binding.appbar)
 
     onBackPressedDispatcher.addCallback(this) {
-      if (binding.nowPlaying.isOpened()) {
-        binding.nowPlaying.close()
+      if (binding.nowPlayingBottomSheet.isOpen) {
+        binding.nowPlayingBottomSheet.close()
       } else {
         navigation.navigateUp()
       }
@@ -121,66 +124,16 @@ class MainActivity : AppCompatActivity() {
   override fun onResume() {
     super.onResume()
 
-    findViewById<DisableableFrameLayout?>(R.id.container)?.apply {
-      setShouldRegisterTouch {
-        if (binding.nowPlaying.isOpened()) {
-          binding.nowPlaying.close()
-          false
-        } else {
-          true
-        }
-      }
-    }
-
-    favoritedRepository.update(this, lifecycleScope)
-
-    startService(Intent(this, PlayerService::class.java))
-    DownloadService.start(this, PinService::class.java)
-
-    CommandBus.send(Command.RefreshService)
-
-    lifecycleScope.launch(IO) {
-      Userinfo.get(this@MainActivity, oAuth)
-    }
-
-    with(binding) {
+    binding.nowPlaying.getFragment<NowPlayingFragment>().apply {
+      favoritedRepository.update(requireContext(), lifecycleScope)
 
-      nowPlayingContainer?.nowPlayingToggle?.setOnClickListener {
-        CommandBus.send(Command.ToggleState)
-      }
+      startService(Intent(requireContext(), PlayerService::class.java))
+      DownloadService.start(requireContext(), PinService::class.java)
 
-      nowPlayingContainer?.nowPlayingNext?.setOnClickListener {
-        CommandBus.send(Command.NextTrack)
-      }
+      CommandBus.send(Command.RefreshService)
 
-      nowPlayingContainer?.nowPlayingDetailsPrevious?.setOnClickListener {
-        CommandBus.send(Command.PreviousTrack)
-      }
-
-      nowPlayingContainer?.nowPlayingDetailsNext?.setOnClickListener {
-        CommandBus.send(Command.NextTrack)
-      }
-
-      nowPlayingContainer?.nowPlayingDetailsToggle?.setOnClickListener {
-        CommandBus.send(Command.ToggleState)
-      }
-
-      binding.nowPlayingContainer?.nowPlayingDetailsProgress?.setOnSeekBarChangeListener(
-        object : SeekBar.OnSeekBarChangeListener {
-          override fun onStopTrackingTouch(view: SeekBar?) {}
-
-          override fun onStartTrackingTouch(view: SeekBar?) {}
-
-          override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) {
-            if (fromUser) {
-              CommandBus.send(Command.Seek(progress))
-            }
-          }
-        })
-
-      landscapeQueue?.let {
-        supportFragmentManager.beginTransaction()
-          .replace(R.id.landscape_queue, LandscapeQueueFragment()).commit()
+      lifecycleScope.launch(IO) {
+        Userinfo.get(this@MainActivity, oAuth)
       }
     }
   }
@@ -223,7 +176,7 @@ class MainActivity : AppCompatActivity() {
   override fun onOptionsItemSelected(item: MenuItem): Boolean {
     when (item.itemId) {
       android.R.id.home -> {
-        binding.nowPlaying.close()
+        binding.nowPlayingBottomSheet.close()
         navigation.popBackStack(R.id.browseFragment, false)
       }
 
@@ -298,70 +251,22 @@ class MainActivity : AppCompatActivity() {
   private fun watchEventBus() {
     lifecycleScope.launch(Main) {
       EventBus.get().collect { event ->
-        if (event is Event.LogOut) {
-          FFA.get().deleteAllData(this@MainActivity)
-          startActivity(
-            Intent(this@MainActivity, LoginActivity::class.java).apply {
-              flags = Intent.FLAG_ACTIVITY_NO_HISTORY
-            }
-          )
-
-          finish()
-        } else if (event is Event.PlaybackError) {
-          toast(event.message)
-        } else if (event is Event.Buffering) {
-          when (event.value) {
-            true -> binding.nowPlayingContainer?.nowPlayingBuffering?.visibility = View.VISIBLE
-            false -> binding.nowPlayingContainer?.nowPlayingBuffering?.visibility = View.GONE
-          }
-        } else if (event is Event.PlaybackStopped) {
-          if (binding.nowPlaying.visibility == View.VISIBLE) {
-            (binding.navHostFragment.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
-              it.bottomMargin = it.bottomMargin / 2
-            }
-
-            binding.landscapeQueue?.let { landscape_queue ->
-              (landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
-                it.bottomMargin = it.bottomMargin / 2
+        when(event) {
+          is Event.LogOut -> logout()
+          is Event.PlaybackError -> toast(event.message)
+          is Event.PlaybackStopped -> binding.nowPlayingBottomSheet.hide()
+          is Event.TrackFinished -> incrementListenCount(event.track)
+          is Event.QueueChanged -> {
+            if(binding.nowPlayingBottomSheet.isHidden) binding.nowPlayingBottomSheet.show()
+            findViewById<View>(R.id.nav_queue)?.let { view ->
+              ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).let {
+                it.duration = 500
+                it.interpolator = AccelerateDecelerateInterpolator()
+                it.start()
               }
             }
-
-            binding.nowPlaying.animate()
-              .alpha(0.0f)
-              .setDuration(400)
-              .setListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animator: Animator) {
-                  binding.nowPlaying.visibility = View.GONE
-                }
-              })
-              .start()
-          }
-        } else if (event is Event.TrackFinished) {
-          incrementListenCount(event.track)
-        } else if (event is Event.StateChanged) {
-          when (event.playing) {
-            true -> {
-              binding.nowPlayingContainer?.nowPlayingToggle?.icon =
-                AppCompatResources.getDrawable(this@MainActivity, R.drawable.pause)
-              binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon =
-                AppCompatResources.getDrawable(this@MainActivity, R.drawable.pause)
-            }
-
-            false -> {
-              binding.nowPlayingContainer?.nowPlayingToggle?.icon =
-                AppCompatResources.getDrawable(this@MainActivity, R.drawable.play)
-              binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon =
-                AppCompatResources.getDrawable(this@MainActivity, R.drawable.play)
-            }
-          }
-        } else if (event is Event.QueueChanged) {
-          findViewById<View>(R.id.nav_queue)?.let { view ->
-            ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).let {
-              it.duration = 500
-              it.interpolator = AccelerateDecelerateInterpolator()
-              it.start()
-            }
           }
+          else -> {}
         }
       }
     }
@@ -402,24 +307,6 @@ class MainActivity : AppCompatActivity() {
         }
       }
     }
-
-    lifecycleScope.launch(Main) {
-      ProgressBus.get().collect { (current, duration, percent) ->
-        binding.nowPlayingContainer?.nowPlayingProgress?.progress = percent
-        binding.nowPlayingContainer?.nowPlayingDetailsProgress?.progress = percent
-
-        val currentMins = (current / 1000) / 60
-        val currentSecs = (current / 1000) % 60
-
-        val durationMins = duration / 60
-        val durationSecs = duration % 60
-
-        binding.nowPlayingContainer?.nowPlayingDetailsProgressCurrent?.text =
-          "%02d:%02d".format(currentMins, currentSecs)
-        binding.nowPlayingContainer?.nowPlayingDetailsProgressDuration?.text =
-          "%02d:%02d".format(durationMins, durationSecs)
-      }
-    }
   }
 
   private fun refreshCurrentTrack(track: Track?) {
@@ -444,175 +331,6 @@ class MainActivity : AppCompatActivity() {
           }
         }
       }
-
-      binding.nowPlayingContainer?.nowPlayingTitle?.text = track.title
-      binding.nowPlayingContainer?.nowPlayingAlbum?.text = track.artist.name
-
-      binding.nowPlayingContainer?.nowPlayingDetailsTitle?.text = track.title
-      binding.nowPlayingContainer?.nowPlayingDetailsArtist?.text = track.artist.name
-
-      val lic = this.layoutInflater.context
-
-      CoverArt.withContext(lic, maybeNormalizeUrl(track.cover()))
-        .fit()
-        .centerCrop()
-        .into(binding.nowPlayingContainer?.nowPlayingCover)
-
-      binding.nowPlayingContainer?.nowPlayingDetailsCover?.let { nowPlayingDetailsCover ->
-        CoverArt.withContext(lic, maybeNormalizeUrl(track.cover()))
-          .fit()
-          .centerCrop()
-          .transform(RoundedCornersTransformation(16, 0))
-          .into(nowPlayingDetailsCover)
-      }
-
-      if (binding.nowPlayingContainer?.nowPlayingCover == null) {
-        lifecycleScope.launch(Default) {
-          val width = DisplayMetrics().apply {
-            windowManager.defaultDisplay.getMetrics(this)
-          }.widthPixels
-
-          val backgroundCover = CoverArt.withContext(lic, maybeNormalizeUrl(track.cover()))
-            .get()
-            .run { Bitmap.createScaledBitmap(this, width, width, false).toDrawable(resources) }
-            .apply {
-              alpha = 20
-              gravity = Gravity.CENTER
-            }
-
-          withContext(Main) {
-            binding.nowPlayingContainer?.nowPlayingDetails?.background = backgroundCover
-          }
-        }
-      }
-
-      binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.let { now_playing_details_repeat ->
-        changeRepeatMode(FFACache.getLine(this@MainActivity, "repeat")?.toInt() ?: 0)
-
-        now_playing_details_repeat.setOnClickListener {
-          val current = FFACache.getLine(this@MainActivity, "repeat")?.toInt() ?: 0
-
-          changeRepeatMode((current + 1) % 3)
-        }
-      }
-
-      binding.nowPlayingContainer?.nowPlayingDetailsInfo?.let { nowPlayingDetailsInfo ->
-        nowPlayingDetailsInfo.setOnClickListener {
-          PopupMenu(
-            this@MainActivity,
-            nowPlayingDetailsInfo,
-            Gravity.START,
-            R.attr.actionOverflowMenuStyle,
-            0
-          ).apply {
-            inflate(R.menu.track_info)
-
-            setOnMenuItemClickListener {
-              when (it.itemId) {
-                R.id.track_info_artist -> BrowseFragmentDirections.browseToAlbums(
-                  track.artist,
-                  track.album?.cover()
-                )
-                R.id.track_info_album -> track.album?.let(BrowseFragmentDirections::browseToTracks)
-                R.id.track_info_details -> TrackInfoDetailsFragment.new(track)
-                  .show(supportFragmentManager, "dialog")
-              }
-
-              binding.nowPlaying.close()
-
-              true
-            }
-
-            show()
-          }
-        }
-      }
-
-      binding.nowPlayingContainer?.nowPlayingDetailsFavorite?.let { now_playing_details_favorite ->
-        favoritedRepository.fetch().untilNetwork(lifecycleScope, IO) { favorites, _, _, _ ->
-          lifecycleScope.launch(Main) {
-            track.favorite = favorites.contains(track.id)
-
-            when (track.favorite) {
-              true -> now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite))
-              false -> now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground))
-            }
-          }
-        }
-
-        now_playing_details_favorite.setOnClickListener {
-          when (track.favorite) {
-            true -> {
-              favoriteRepository.deleteFavorite(track.id)
-              now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground))
-            }
-
-            false -> {
-              favoriteRepository.addFavorite(track.id)
-              now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite))
-            }
-          }
-
-          track.favorite = !track.favorite
-
-          favoriteRepository.fetch(Repository.Origin.Network.origin)
-        }
-
-        binding.nowPlayingContainer?.nowPlayingDetailsAddToPlaylist?.setOnClickListener {
-          CommandBus.send(Command.AddToPlaylist(listOf(track)))
-        }
-      }
-    }
-  }
-
-  private fun changeRepeatMode(index: Int) {
-    when (index) {
-      // From no repeat to repeat all
-      0 -> {
-        FFACache.set(this@MainActivity, "repeat", "0")
-
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat)
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter(
-          ContextCompat.getColor(
-            this,
-            R.color.controlForeground
-          )
-        )
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 0.2f
-
-        CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_OFF))
-      }
-
-      // From repeat all to repeat one
-      1 -> {
-        FFACache.set(this@MainActivity, "repeat", "1")
-
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat)
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter(
-          ContextCompat.getColor(
-            this,
-            R.color.controlForeground
-          )
-        )
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 1.0f
-
-        CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ALL))
-      }
-
-      // From repeat one to no repeat
-      2 -> {
-        FFACache.set(this@MainActivity, "repeat", "2")
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setImageResource(R.drawable.repeat_one)
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.setColorFilter(
-          ContextCompat.getColor(
-            this,
-            R.color.controlForeground
-          )
-        )
-        binding.nowPlayingContainer?.nowPlayingDetailsRepeat?.alpha = 1.0f
-
-        CommandBus.send(Command.SetRepeatMode(Player.REPEAT_MODE_ONE))
-      }
     }
   }
 
@@ -633,4 +351,15 @@ class MainActivity : AppCompatActivity() {
       }
     }
   }
+
+  private fun logout() {
+    FFA.get().deleteAllData(this@MainActivity)
+    startActivity(
+      Intent(this@MainActivity, LoginActivity::class.java).apply {
+        flags = Intent.FLAG_ACTIVITY_NO_HISTORY
+      }
+    )
+
+    finish()
+  }
 }
diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt
index 2e001745c9608f1f63d33061168c5b0176d43f6f..e3766172dffdaed430c46317810d6c31cb4bf7cf 100644
--- a/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/adapters/AlbumsGridAdapter.kt
@@ -41,7 +41,6 @@ class AlbumsGridAdapter(
 
     CoverArt.withContext(layoutInflater.context, maybeNormalizeUrl(album.cover()))
       .fit()
-      .placeholder(R.drawable.cover)
       .transform(RoundedCornersTransformation(16, 0))
       .into(holder.cover)
 
diff --git a/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt b/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt
index b8cb5ba97e8a4e7dc3c432f091f62aa799e2db20..65caed3588f5e0e61460da81ec89419712ba6665 100644
--- a/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/adapters/BrowseTabsAdapter.kt
@@ -9,29 +9,16 @@ import audio.funkwhale.ffa.fragments.FavoritesFragment
 import audio.funkwhale.ffa.fragments.PlaylistsFragment
 import audio.funkwhale.ffa.fragments.RadiosFragment
 
-class BrowseTabsAdapter(val context: Fragment) :
-  FragmentStateAdapter(context) {
-  var tabs = mutableListOf<Fragment>()
-
+class BrowseTabsAdapter(val context: Fragment) : FragmentStateAdapter(context) {
   override fun getItemCount() = 5
 
-  override fun createFragment(position: Int): Fragment {
-    tabs.getOrNull(position)?.let {
-      return it
-    }
-
-    val fragment = when (position) {
-      0 -> ArtistsFragment()
-      1 -> AlbumsGridFragment()
-      2 -> PlaylistsFragment()
-      3 -> RadiosFragment()
-      4 -> FavoritesFragment()
-      else -> ArtistsFragment()
-    }
-
-    tabs.add(position, fragment)
-
-    return fragment
+  override fun createFragment(position: Int): Fragment = when (position) {
+    0 -> ArtistsFragment()
+    1 -> AlbumsGridFragment()
+    2 -> PlaylistsFragment()
+    3 -> RadiosFragment()
+    4 -> FavoritesFragment()
+    else -> ArtistsFragment()
   }
 
   fun tabText(position: Int): String {
diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..539d6526bcfa9aa3862850fffc3bfc61e9457e2f
--- /dev/null
+++ b/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt
@@ -0,0 +1,250 @@
+package audio.funkwhale.ffa.fragments
+
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.widget.Button
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.appcompat.widget.PopupMenu
+import androidx.customview.widget.Openable
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.distinctUntilChanged
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import audio.funkwhale.ffa.MainNavDirections
+import audio.funkwhale.ffa.R
+import audio.funkwhale.ffa.databinding.FragmentNowPlayingBinding
+import audio.funkwhale.ffa.model.Track
+import audio.funkwhale.ffa.repositories.FavoritedRepository
+import audio.funkwhale.ffa.repositories.FavoritesRepository
+import audio.funkwhale.ffa.repositories.Repository
+import audio.funkwhale.ffa.utils.*
+import audio.funkwhale.ffa.viewmodel.NowPlayingViewModel
+import audio.funkwhale.ffa.views.NowPlayingBottomSheet
+import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
+  private val binding by lazy { FragmentNowPlayingBinding.bind(requireView()) }
+  private val viewModel by viewModels<NowPlayingViewModel>()
+  private val favoriteRepository by lazy { FavoritesRepository(requireContext()) }
+  private val favoritedRepository by lazy { FavoritedRepository(requireContext()) }
+
+  private val bottomSheet: BottomSheetIneractable? by lazy {
+    var view = this.view?.parent
+    while (view != null) {
+      if(view is BottomSheetIneractable) return@lazy view
+      view = view.parent
+    }
+    null
+  }
+
+  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+    binding.lifecycleOwner = viewLifecycleOwner
+
+    viewModel.currentTrack.distinctUntilChanged().observe(viewLifecycleOwner, ::onTrackChange)
+
+    with(binding.controls) {
+      currentTrackTitle = viewModel.currentTrackTitle
+      currentTrackArtist = viewModel.currentTrackArtist
+      isCurrentTrackFavorite = viewModel.isCurrentTrackFavorite
+      repeatModeResource = viewModel.repeatModeResource
+      repeatModeAlpha = viewModel.repeatModeAlpha
+      currentProgressText = viewModel.currentProgressText
+      currentDurationText = viewModel.currentDurationText
+      isPlaying = viewModel.isPlaying
+      progress = viewModel.progress
+
+      nowPlayingDetailsPrevious.setOnClickListener {
+        CommandBus.send(Command.PreviousTrack)
+      }
+
+      nowPlayingDetailsNext.setOnClickListener {
+        CommandBus.send(Command.NextTrack)
+      }
+
+      nowPlayingDetailsToggle.setOnClickListener {
+        CommandBus.send(Command.ToggleState)
+      }
+
+      nowPlayingDetailsRepeat.setOnClickListener { toggleRepeatMode() }
+      nowPlayingDetailsProgress.setOnSeekBarChangeListener(OnSeekBarChanged())
+      nowPlayingDetailsFavorite.setOnClickListener { onFavorite() }
+      nowPlayingDetailsAddToPlaylist.setOnClickListener { onAddToPlaylist() }
+    }
+
+    with(binding.header) {
+      isBuffering = viewModel.isBuffering
+      isPlaying = viewModel.isPlaying
+      progress = viewModel.progress
+      currentTrackTitle = viewModel.currentTrackTitle
+      currentTrackArtist = viewModel.currentTrackArtist
+
+
+      nowPlayingNext.setOnClickListener {
+        CommandBus.send(Command.NextTrack)
+      }
+
+      nowPlayingToggle.setOnClickListener {
+        CommandBus.send(Command.ToggleState)
+      }
+    }
+
+    binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() }
+
+    lifecycleScope.launch(Dispatchers.Main) {
+      CommandBus.get().collect { onCommand(it) }
+    }
+
+    lifecycleScope.launch(Dispatchers.Main) {
+      EventBus.get().collect { onEvent(it) }
+    }
+
+    lifecycleScope.launch(Dispatchers.Main) {
+      ProgressBus.get().collect { onProgress(it) }
+    }
+  }
+
+
+  private fun toggleRepeatMode() {
+    val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0)
+    val iteratedRepeatMode = (cachedRepeatMode + 1) % 3
+    FFACache.set(requireContext(), "repeat", "$iteratedRepeatMode")
+    CommandBus.send(Command.SetRepeatMode(iteratedRepeatMode))
+  }
+
+  private fun onAddToPlaylist() {
+    val currentTrack = viewModel.currentTrack.value ?: return
+    CommandBus.send(Command.AddToPlaylist(listOf(currentTrack)))
+  }
+
+  private fun onCommand(command: Command) = when (command) {
+    is Command.RefreshTrack -> refreshCurrentTrack(command.track)
+    is Command.SetRepeatMode -> viewModel.repeatMode.postValue(command.mode)
+    else -> {}
+  }
+
+  private fun onEvent(event: Event): Unit = when (event) {
+    is Event.Buffering -> viewModel.isBuffering.postValue(event.value)
+    is Event.StateChanged -> viewModel.isPlaying.postValue(event.playing)
+    else -> {}
+  }
+
+  private fun onFavorite() {
+    val currentTrack = viewModel.currentTrack.value ?: return
+
+    if (currentTrack.favorite) favoriteRepository.deleteFavorite(currentTrack.id)
+    else favoriteRepository.addFavorite(currentTrack.id)
+
+    currentTrack.favorite = !currentTrack.favorite
+    // Trigger UI refresh
+    viewModel.currentTrack.postValue(viewModel.currentTrack.value)
+
+    favoritedRepository.fetch(Repository.Origin.Network.origin)
+  }
+
+  private fun onProgress(state: Triple<Int, Int, Int>) {
+    val (current, duration, percent) = state
+
+    val currentMins = (current / 1000) / 60
+    val currentSecs = (current / 1000) % 60
+
+    val durationMins = duration / 60
+    val durationSecs = duration % 60
+
+    viewModel.progress.postValue(percent)
+    viewModel.currentProgressText.postValue("%02d:%02d".format(currentMins, currentSecs))
+    viewModel.currentDurationText.postValue("%02d:%02d".format(durationMins, durationSecs))
+  }
+
+  private fun onTrackChange(track: Track?) {
+    if (track == null) {
+      binding.header.nowPlayingCover.setImageResource(R.drawable.cover)
+      binding.nowPlayingDetailCover.setImageResource(R.drawable.cover)
+      return
+    }
+
+    CoverArt.withContext(requireContext(), maybeNormalizeUrl(track.album?.cover()))
+      .fit()
+      .centerCrop()
+      .into(binding.nowPlayingDetailCover)
+
+    CoverArt.withContext(requireContext(), maybeNormalizeUrl(track.album?.cover()))
+      .fit()
+      .centerCrop()
+      .transform(RoundedCornersTransformation(16, 0))
+      .into(binding.header.nowPlayingCover)
+  }
+
+  private fun openInfoMenu() {
+    val currentTrack = viewModel.currentTrack.value ?: return
+
+    PopupMenu(
+      requireContext(),
+      binding.nowPlayingDetailsInfo,
+      Gravity.START,
+      R.attr.actionOverflowMenuStyle,
+      0
+    ).apply {
+      inflate(R.menu.track_info)
+
+      setOnMenuItemClickListener {
+        bottomSheet?.close()
+
+        when (it.itemId) {
+          R.id.track_info_artist -> findNavController().navigate(
+            MainNavDirections.globalBrowseToAlbums(
+              currentTrack.artist,
+              currentTrack.album?.cover()
+            )
+          )
+          R.id.track_info_album -> currentTrack.album?.let { album ->
+            findNavController().navigate(MainNavDirections.globalBrowseTracks(album))
+          }
+          R.id.track_info_details -> TrackInfoDetailsFragment.new(currentTrack).show(
+            requireActivity().supportFragmentManager, "dialog"
+          )
+        }
+
+        true
+      }
+
+      show()
+    }
+  }
+
+  private fun refreshCurrentTrack(track: Track?) {
+    viewModel.currentTrack.postValue(track)
+
+    val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0)
+    viewModel.repeatMode.postValue(cachedRepeatMode % 3)
+
+    // At this point, a non-null track is required
+
+    if (track == null) return
+
+    favoritedRepository.fetch().untilNetwork(lifecycleScope, Dispatchers.IO) { favorites, _, _, _ ->
+      lifecycleScope.launch(Dispatchers.Main) {
+        track.favorite = favorites.contains(track.id)
+        // Trigger UI refresh
+        viewModel.currentTrack.postValue(viewModel.currentTrack.value)
+      }
+    }
+  }
+
+  inner class OnSeekBarChanged : OnSeekBarChangeListener {
+    override fun onStopTrackingTouch(view: SeekBar?) {}
+
+    override fun onStartTrackingTouch(view: SeekBar?) {}
+
+    override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) {
+      if (fromUser) {
+        CommandBus.send(Command.Seek(progress))
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/BottomSheetIneractable.kt b/app/src/main/java/audio/funkwhale/ffa/utils/BottomSheetIneractable.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f8645914b494f1af3778f57d71fc73c89af5614b
--- /dev/null
+++ b/app/src/main/java/audio/funkwhale/ffa/utils/BottomSheetIneractable.kt
@@ -0,0 +1,10 @@
+package audio.funkwhale.ffa.utils
+
+import androidx.customview.widget.Openable
+
+interface BottomSheetIneractable: Openable {
+  val isHidden: Boolean
+  fun show()
+  fun hide()
+  fun toggle()
+}
\ No newline at end of file
diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt b/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt
index 3cd73d932995128a2b121e9302d1ccb324825309..85b137f60fb5ab84f49da744ad5a5a93febdd002 100644
--- a/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/utils/CoverArt.kt
@@ -2,7 +2,9 @@ package audio.funkwhale.ffa.utils
 
 import android.content.Context
 import android.net.Uri
+import android.transition.CircularPropagation
 import android.util.Log
+import androidx.swiperefreshlayout.widget.CircularProgressDrawable
 import audio.funkwhale.ffa.BuildConfig
 import audio.funkwhale.ffa.R
 import com.squareup.picasso.Downloader
@@ -252,9 +254,10 @@ open class CoverArt private constructor() {
      * The primary entrypoint for the codebase.
      */
     fun withContext(context: Context, url: String?): RequestCreator {
-      return buildPicasso(context)
-        .load(url)
-        .placeholder(R.drawable.cover)
+      val request = buildPicasso(context).load(url)
+      if(url == null) request.placeholder(R.drawable.cover)
+      else request.placeholder(CircularProgressDrawable(context))
+      return request.error(R.drawable.cover)
     }
   }
 }
diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt
index 9a81a6d062902636b9f7d62a93406abf38dfcf08..2e5371203bbfc06b43474cf75291219c0ed44377 100644
--- a/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/utils/Extensions.kt
@@ -149,3 +149,5 @@ inline fun <T, U, V, W, R> LiveData<T>.mergeWith(
     }
   }
 }
+
+public fun String?.toIntOrElse(default: Int): Int = this?.toIntOrNull(radix = 10) ?: default
\ No newline at end of file
diff --git a/app/src/main/java/audio/funkwhale/ffa/utils/ViewBindings.kt b/app/src/main/java/audio/funkwhale/ffa/utils/ViewBindings.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6a12db73f42ba9d37a42d433818a3b2318997614
--- /dev/null
+++ b/app/src/main/java/audio/funkwhale/ffa/utils/ViewBindings.kt
@@ -0,0 +1,25 @@
+package audio.funkwhale.ffa.utils
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.widget.ImageButton
+import androidx.annotation.ColorRes
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.databinding.BindingAdapter
+
+
+@BindingAdapter("srcCompat")
+fun setImageViewResource(imageView: AppCompatImageView, resource: Any?) = when (resource) {
+  is Bitmap -> imageView.setImageBitmap(resource)
+  is Int -> imageView.setImageResource(resource)
+  is Drawable -> imageView.setImageDrawable(resource)
+  else -> imageView.setImageDrawable(ColorDrawable(Color.TRANSPARENT))
+}
+
+@BindingAdapter("tint")
+fun setTint(imageView: ImageButton, @ColorRes resource: Int) = resource.let {
+  imageView.setColorFilter(resource)
+}
\ No newline at end of file
diff --git a/app/src/main/java/audio/funkwhale/ffa/viewmodel/NowPlayingViewModel.kt b/app/src/main/java/audio/funkwhale/ffa/viewmodel/NowPlayingViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4a972aee179d53d6ea6888223f79e143a4c6a4b9
--- /dev/null
+++ b/app/src/main/java/audio/funkwhale/ffa/viewmodel/NowPlayingViewModel.kt
@@ -0,0 +1,56 @@
+package audio.funkwhale.ffa.viewmodel
+
+import android.app.Application
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.graphics.drawable.toDrawable
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.distinctUntilChanged
+import androidx.lifecycle.map
+import audio.funkwhale.ffa.FFA
+import audio.funkwhale.ffa.R
+import audio.funkwhale.ffa.model.Track
+import audio.funkwhale.ffa.utils.CoverArt
+import audio.funkwhale.ffa.utils.maybeNormalizeUrl
+import com.google.android.exoplayer2.Player
+import com.squareup.picasso.Picasso
+import com.squareup.picasso.Target
+
+class NowPlayingViewModel(app: Application) : AndroidViewModel(app) {
+  val isBuffering = MutableLiveData(false)
+  val isPlaying = MutableLiveData(false)
+  val repeatMode = MutableLiveData(0)
+  val progress = MutableLiveData(0)
+  val currentTrack = MutableLiveData<Track?>(null)
+  val currentProgressText = MutableLiveData("")
+  val currentDurationText = MutableLiveData("")
+
+  // Calling distinctUntilChanged() prevents triggering an event when the track hasn't changed
+  val currentTrackTitle = currentTrack.distinctUntilChanged().map { it?.title ?: "" }
+  val currentTrackArtist = currentTrack.distinctUntilChanged().map { it?.artist?.name ?: "" }
+  // Not calling distinctUntilChanged() here as we need to process every event
+  val isCurrentTrackFavorite = currentTrack.map {
+    it?.favorite ?: false
+  }
+
+  val repeatModeResource = repeatMode.distinctUntilChanged().map {
+    when (it) {
+      Player.REPEAT_MODE_ONE -> AppCompatResources.getDrawable(context, R.drawable.repeat_one)
+      else -> AppCompatResources.getDrawable(context, R.drawable.repeat)
+    }
+  }
+
+  val repeatModeAlpha = repeatMode.distinctUntilChanged().map {
+    when (it) {
+      Player.REPEAT_MODE_OFF -> 0.2f
+      else -> 1f
+    }
+  }
+
+  private val context: Context
+    get() = getApplication<FFA>().applicationContext
+}
diff --git a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b0e079fb14e410706d784ec9e286026e8c9d8aa9
--- /dev/null
+++ b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt
@@ -0,0 +1,67 @@
+package audio.funkwhale.ffa.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import audio.funkwhale.ffa.R
+import audio.funkwhale.ffa.utils.BottomSheetIneractable
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.card.MaterialCardView
+
+class NowPlayingBottomSheet @JvmOverloads constructor (
+  context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : MaterialCardView(context, attrs), BottomSheetIneractable {
+  val behavior = BottomSheetBehavior<NowPlayingBottomSheet>(context, attrs)
+
+  private val targetHeaderId: Int
+
+
+  init {
+    context.theme.obtainStyledAttributes(attrs, R.styleable.NowPlaying, defStyleAttr, 0).use {
+      targetHeaderId = it.getResourceId(R.styleable.NowPlaying_target_header, NO_ID)
+    }
+  }
+
+  override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
+    super.setLayoutParams(params)
+    (params as CoordinatorLayout.LayoutParams).behavior = behavior
+  }
+
+  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+    findViewById<View>(targetHeaderId)?.apply {
+      behavior.setPeekHeight(this.measuredHeight, false)
+      this.setOnClickListener { this@NowPlayingBottomSheet.toggle() }
+    } ?: hide()
+  }
+
+  // Bottom sheet interactions
+  override val isHidden: Boolean get() = behavior.state == BottomSheetBehavior.STATE_HIDDEN
+
+  override fun isOpen(): Boolean = behavior.state == BottomSheetBehavior.STATE_EXPANDED
+
+  override fun open() {
+    behavior.state = BottomSheetBehavior.STATE_EXPANDED
+  }
+
+  override fun close() {
+    behavior.state = BottomSheetBehavior.STATE_COLLAPSED
+  }
+
+  override fun show() {
+    behavior.isHideable = false
+    close()
+  }
+
+  override fun hide() {
+    behavior.isHideable = true
+    behavior.state = BottomSheetBehavior.STATE_HIDDEN
+  }
+
+  override fun toggle() {
+    if (isHidden) return
+    if (isOpen) close() else open()
+  }
+}
diff --git a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt
deleted file mode 100644
index 7ad73746cc1bf461c4776b206cebdac4283b8b64..0000000000000000000000000000000000000000
--- a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingView.kt
+++ /dev/null
@@ -1,255 +0,0 @@
-package audio.funkwhale.ffa.views
-
-import android.animation.ValueAnimator
-import android.content.Context
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.GestureDetector
-import android.view.LayoutInflater
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.animation.DecelerateInterpolator
-import audio.funkwhale.ffa.R
-import audio.funkwhale.ffa.databinding.PartialNowPlayingBinding
-import com.google.android.material.card.MaterialCardView
-import kotlin.math.abs
-import kotlin.math.min
-
-class NowPlayingView : MaterialCardView {
-  val activity: Context
-  var gestureDetector: GestureDetector? = null
-  var gestureDetectorCallback: OnGestureDetection? = null
-
-  private val binding =
-    PartialNowPlayingBinding.inflate(LayoutInflater.from(context), this, true)
-
-  constructor(context: Context) : super(context) {
-    activity = context
-  }
-
-  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
-    activity = context
-  }
-
-  constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) {
-    activity = context
-  }
-
-  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
-
-    binding.nowPlayingRoot.measure(
-      widthMeasureSpec,
-      MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED)
-    )
-  }
-
-  override fun onVisibilityChanged(changedView: View, visibility: Int) {
-    super.onVisibilityChanged(changedView, visibility)
-
-    if (visibility == View.VISIBLE && gestureDetector == null) {
-      viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
-        override fun onGlobalLayout() {
-          gestureDetectorCallback = OnGestureDetection()
-          gestureDetector = GestureDetector(context, gestureDetectorCallback!!)
-
-          setOnTouchListener { _, motionEvent ->
-            val ret = gestureDetector?.onTouchEvent(motionEvent) ?: false
-
-            if (motionEvent.actionMasked == MotionEvent.ACTION_UP) {
-              if (gestureDetectorCallback?.isScrolling == true) {
-                gestureDetectorCallback?.onUp()
-              }
-            }
-            performClick()
-            ret
-          }
-
-          viewTreeObserver.removeOnGlobalLayoutListener(this)
-        }
-      })
-    }
-  }
-
-  fun isOpened(): Boolean = gestureDetectorCallback?.isOpened() ?: false
-
-  fun close() {
-    gestureDetectorCallback?.close()
-  }
-
-  inner class OnGestureDetection : GestureDetector.SimpleOnGestureListener() {
-    private var maxHeight = 0
-    private var minHeight = 0
-    private var maxMargin = 0
-
-    private var initialTouchY = 0f
-    private var lastTouchY = 0f
-
-    var isScrolling = false
-    private var flingAnimator: ValueAnimator? = null
-
-    init {
-      (layoutParams as? MarginLayoutParams)?.let {
-        maxMargin = it.marginStart
-      }
-
-      minHeight = TypedValue().let {
-        activity.theme.resolveAttribute(R.attr.actionBarSize, it, true)
-
-        TypedValue.complexToDimensionPixelSize(it.data, resources.displayMetrics)
-      }
-
-      maxHeight = binding.nowPlayingDetails.measuredHeight + (2 * maxMargin)
-    }
-
-    override fun onDown(e: MotionEvent): Boolean {
-      initialTouchY = e.rawY
-      lastTouchY = e.rawY
-
-      return true
-    }
-
-    fun onUp(): Boolean {
-      isScrolling = false
-
-      layoutParams.let {
-        val offsetToMax = maxHeight - height
-        val offsetToMin = height - minHeight
-
-        flingAnimator =
-          if (offsetToMin < offsetToMax) ValueAnimator.ofInt(it.height, minHeight)
-          else ValueAnimator.ofInt(it.height, maxHeight)
-
-        animateFling(500)
-
-        return true
-      }
-    }
-
-    override fun onFling(
-      firstMotionEvent: MotionEvent,
-      secondMotionEvent: MotionEvent,
-      velocityX: Float,
-      velocityY: Float
-    ): Boolean {
-      isScrolling = false
-
-      layoutParams.let {
-        val diff =
-          if (velocityY < 0) maxHeight - it.height
-          else it.height - minHeight
-
-        flingAnimator =
-          if (velocityY < 0) ValueAnimator.ofInt(it.height, maxHeight)
-          else ValueAnimator.ofInt(it.height, minHeight)
-
-        animateFling(min(abs((diff.toFloat() / velocityY * 1000).toLong()), 600))
-      }
-
-      return true
-    }
-
-    override fun onScroll(
-      firstMotionEvent: MotionEvent,
-      secondMotionEvent: MotionEvent,
-      distanceX: Float,
-      distanceY: Float
-    ): Boolean {
-      isScrolling = true
-
-      layoutParams.let {
-        val newHeight = it.height + lastTouchY - secondMotionEvent.rawY
-        val progress = (newHeight - minHeight) / (maxHeight - minHeight)
-        val newMargin = maxMargin - (maxMargin * progress)
-
-        (layoutParams as? MarginLayoutParams)?.let { params ->
-          params.marginStart = newMargin.toInt()
-          params.marginEnd = newMargin.toInt()
-          params.bottomMargin = newMargin.toInt()
-        }
-
-        layoutParams = layoutParams.apply {
-          when {
-            newHeight <= minHeight -> {
-              height = minHeight
-              return true
-            }
-            newHeight >= maxHeight -> {
-              height = maxHeight
-              return true
-            }
-            else -> height = newHeight.toInt()
-          }
-        }
-
-        binding.summary.alpha = 1f - progress
-
-        binding.summary.layoutParams = binding.summary.layoutParams.apply {
-          height = (minHeight * (1f - progress)).toInt()
-        }
-      }
-
-      lastTouchY = secondMotionEvent.rawY
-
-      return true
-    }
-
-    override fun onSingleTapUp(e: MotionEvent): Boolean {
-      layoutParams.let {
-        if (height != minHeight) return true
-
-        flingAnimator = ValueAnimator.ofInt(it.height, maxHeight)
-
-        animateFling(300)
-      }
-
-      return true
-    }
-
-    fun isOpened(): Boolean = layoutParams.height == maxHeight
-
-    fun close(): Boolean {
-      layoutParams.let {
-        if (it.height == minHeight) return true
-
-        flingAnimator = ValueAnimator.ofInt(it.height, minHeight)
-
-        animateFling(300)
-      }
-
-      return true
-    }
-
-    private fun animateFling(dur: Long) {
-      flingAnimator?.apply {
-        duration = dur
-        interpolator = DecelerateInterpolator()
-
-        addUpdateListener { valueAnimator ->
-          layoutParams = layoutParams.apply {
-            val newHeight = valueAnimator.animatedValue as Int
-            val progress = (newHeight.toFloat() - minHeight) / (maxHeight - minHeight)
-            val newMargin = maxMargin - (maxMargin * progress)
-
-            (layoutParams as? MarginLayoutParams)?.let {
-              it.marginStart = newMargin.toInt()
-              it.marginEnd = newMargin.toInt()
-              it.bottomMargin = newMargin.toInt()
-            }
-
-            height = newHeight
-
-            binding.summary.alpha = 1f - progress
-
-            binding.summary.layoutParams = binding.summary.layoutParams.apply {
-              height = (minHeight * (1f - progress)).toInt()
-            }
-          }
-        }
-
-        start()
-      }
-    }
-  }
-}
diff --git a/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt b/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt
index 586131ab689822d55bc2d418cda6c4325dc144e1..e148e105a7ded4cdbbb092087256307eed9e9dd6 100644
--- a/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt
+++ b/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt
@@ -4,7 +4,7 @@ import android.content.Context
 import android.util.AttributeSet
 import androidx.appcompat.widget.AppCompatImageView
 
-class SquareImageView : AppCompatImageView {
+open class SquareImageView : AppCompatImageView {
   constructor(context: Context) : super(context)
   constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
   constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style)
@@ -12,6 +12,8 @@ class SquareImageView : AppCompatImageView {
   override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec)
 
-    setMeasuredDimension(measuredWidth, measuredWidth)
+    val dimension = if(measuredWidth == 0 && measuredHeight > 0) measuredHeight else measuredWidth
+
+    setMeasuredDimension(dimension, dimension)
   }
 }
diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml
index 4834d5c5c8804a1ebfe6797f93b5853cae18dbe8..30c86da0500d767c45d897919dbce0d5cc5f8434 100644
--- a/app/src/main/res/layout-land/activity_main.xml
+++ b/app/src/main/res/layout-land/activity_main.xml
@@ -1,64 +1,82 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout
+  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:layout_width="match_parent"
   android:layout_height="match_parent">
 
-  <LinearLayout
+  <androidx.coordinatorlayout.widget.CoordinatorLayout
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:baselineAligned="false"
-    android:orientation="horizontal">
+    android:layout_height="0dp"
+    android:background="@color/surface"
+    app:layout_constraintTop_toTopOf="parent"
+    app:layout_constraintVertical_weight="10">
+    <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:baselineAligned="false"
+      android:orientation="horizontal">
 
-    <androidx.fragment.app.FragmentContainerView
+      <androidx.fragment.app.FragmentContainerView
         android:id="@+id/nav_host_fragment"
         android:name="androidx.navigation.fragment.NavHostFragment"
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_weight="1"
-        android:layout_marginBottom="?attr/actionBarSize"
         app:defaultNavHost="true"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
         app:navGraph="@navigation/main_nav"
-        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
-
-    <FrameLayout
-      android:id="@+id/landscape_queue"
-      android:layout_width="0dp"
-      android:layout_height="match_parent"
-      android:layout_marginBottom="?attr/actionBarSize"
-      android:layout_weight="1"
-      app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+        tools:layout="@layout/fragment_artists" />
 
-  </LinearLayout>
+      <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/landscape_queue"
+        android:name="audio.funkwhale.ffa.fragments.LandscapeQueueFragment"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
+        tools:layout="@layout/partial_queue" />
+    </LinearLayout>
+  </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
-  <audio.funkwhale.ffa.views.NowPlayingView
-    android:id="@+id/now_playing"
+  <androidx.coordinatorlayout.widget.CoordinatorLayout
     android:layout_width="match_parent"
-    android:layout_height="?attr/actionBarSize"
-    android:layout_gravity="bottom"
-    android:layout_margin="8dp"
-    android:alpha="0"
-    android:visibility="gone"
-    app:cardCornerRadius="8dp"
-    app:cardElevation="12dp"
-    app:layout_dodgeInsetEdges="bottom"
-    tools:alpha="1"
-    tools:visibility="visible">
-
-    <include layout="@layout/partial_now_playing" />
-
-  </audio.funkwhale.ffa.views.NowPlayingView>
+    android:layout_height="0dp"
+    app:layout_constraintBottom_toTopOf="@id/appbar_wrapper"
+    app:layout_constraintTop_toTopOf="parent">
+    <audio.funkwhale.ffa.views.NowPlayingBottomSheet
+      android:id="@+id/now_playing_bottom_sheet"
+      style="?attr/bottomSheetStyle"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      app:cardCornerRadius="3dp"
+      app:cardElevation="12dp"
+      app:target_header="@id/header">
+      <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/now_playing"
+        android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:layout="@layout/fragment_now_playing" />
+    </audio.funkwhale.ffa.views.NowPlayingBottomSheet>
+  </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
-  <com.google.android.material.bottomappbar.BottomAppBar
-    android:id="@+id/appbar"
+  <androidx.coordinatorlayout.widget.CoordinatorLayout
+    android:id="@+id/appbar_wrapper"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_gravity="bottom"
-    android:theme="@style/AppTheme.AppBar"
-    app:backgroundTint="@color/colorPrimaryDark"
-    app:layout_insetEdge="bottom"
-    app:navigationIcon="@drawable/funkwhaleshape"
-    tools:menu="@menu/toolbar" />
+    app:layout_constraintBottom_toBottomOf="parent">
 
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+    <com.google.android.material.bottomappbar.BottomAppBar
+      android:id="@+id/appbar"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_gravity="bottom"
+      android:theme="@style/AppTheme.AppBar"
+      app:backgroundTint="@color/elevatedSurface"
+      app:layout_insetEdge="bottom"
+      app:navigationIcon="@drawable/funkwhaleshape"
+      tools:menu="@menu/toolbar" />
+  </androidx.coordinatorlayout.widget.CoordinatorLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout-land/fragment_now_playing.xml b/app/src/main/res/layout-land/fragment_now_playing.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4480aaf4a9d70972ac4dff32b225e7d987639b9a
--- /dev/null
+++ b/app/src/main/res/layout-land/fragment_now_playing.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  xmlns:tools="http://schemas.android.com/tools">
+  <androidx.constraintlayout.widget.ConstraintLayout
+    android:id="@+id/now_playing_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/elevatedSurface">
+
+    <include
+      android:id="@+id/header"
+      layout="@layout/partial_now_playing_header" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+      android:id="@+id/cover_container"
+      android:layout_width="wrap_content"
+      android:layout_height="0dp"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/header">
+
+      <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/now_playing_detail_cover"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:scaleType="fitCenter"
+        app:layout_constraintDimensionRatio="H,1:1"
+        app:layout_constraintStart_toStartOf="parent"
+        app:srcCompat="@drawable/cover"
+        tools:src="@tools:sample/avatars" />
+
+      <ImageButton
+        android:id="@+id/now_playing_details_info"
+        style="@style/IconButton"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_gravity="top|end"
+        android:layout_margin="8dp"
+        android:background="@drawable/circle"
+        android:contentDescription="@string/alt_track_info"
+        android:src="@drawable/more"
+        app:layout_constraintEnd_toEndOf="@id/now_playing_detail_cover"
+        app:layout_constraintTop_toTopOf="@id/now_playing_detail_cover"
+        app:tint="@color/controlForeground" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+    <include
+      android:id="@+id/controls"
+      layout="@layout/partial_now_playing_controls"
+      android:layout_width="0dp"
+      android:layout_height="0dp"
+      android:layout_marginStart="4dp"
+      android:layout_marginEnd="4dp"
+      app:layout_constraintTop_toBottomOf="@id/header"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toEndOf="@id/cover_container"
+    />
+
+  </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/partial_now_playing.xml b/app/src/main/res/layout-land/partial_now_playing.xml
deleted file mode 100644
index 03e622910e66c82b54eaeb5c5a8bf23bb346be90..0000000000000000000000000000000000000000
--- a/app/src/main/res/layout-land/partial_now_playing.xml
+++ /dev/null
@@ -1,251 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout 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/now_playing_root"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="@color/elevatedSurface"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:id="@+id/summary"
-        android:layout_width="match_parent"
-        android:layout_height="?attr/actionBarSize"
-        android:orientation="vertical">
-
-        <ProgressBar
-            android:id="@+id/now_playing_progress"
-            style="@android:style/Widget.Material.ProgressBar.Horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="-6dp"
-            android:layout_marginBottom="-6dp"
-            android:progress="40"
-            android:progressTint="@color/colorPrimary" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_vertical"
-            android:orientation="horizontal">
-
-            <FrameLayout
-                android:layout_width="?attr/actionBarSize"
-                android:layout_height="?attr/actionBarSize"
-                android:layout_marginEnd="16dp">
-
-                <audio.funkwhale.ffa.views.SquareImageView
-                    android:id="@+id/now_playing_cover"
-                    android:layout_width="?attr/actionBarSize"
-                    android:layout_height="?attr/actionBarSize"
-                    tools:src="@tools:sample/avatars" />
-
-                <ProgressBar
-                    android:id="@+id/now_playing_buffering"
-                    android:layout_width="?attr/actionBarSize"
-                    android:layout_height="?attr/actionBarSize"
-                    android:indeterminate="true"
-                    android:indeterminateTint="@color/controlForeground"
-                    android:visibility="gone" />
-
-            </FrameLayout>
-
-            <LinearLayout
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:layout_weight="2"
-                android:orientation="vertical">
-
-                <TextView
-                    android:id="@+id/now_playing_title"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:textColor="@color/itemTitle"
-                    tools:text="Supermassive Black Hole" />
-
-                <TextView
-                    android:id="@+id/now_playing_album"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    tools:text="Muse" />
-
-            </LinearLayout>
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/now_playing_toggle"
-                style="@style/AppTheme.OutlinedButton"
-                android:layout_width="?attr/actionBarSize"
-                android:layout_height="match_parent"
-                android:layout_marginEnd="16dp"
-                app:icon="@drawable/play" />
-
-            <ImageButton
-                android:id="@+id/now_playing_next"
-                style="@style/IconButton"
-                android:layout_width="32dp"
-                android:layout_height="32dp"
-                android:layout_marginEnd="16dp"
-                android:contentDescription="@string/control_next"
-                android:src="@drawable/next" />
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/now_playing_details"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:paddingStart="32dp"
-        android:paddingTop="16dp"
-        android:paddingEnd="32dp">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:orientation="vertical">
-
-                <TextView
-                    android:id="@+id/now_playing_details_title"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:textColor="@color/itemTitle"
-                    android:textSize="18sp"
-                    tools:text="Supermassive Black Hole" />
-
-                <TextView
-                    android:id="@+id/now_playing_details_artist"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    tools:text="Muse" />
-
-            </LinearLayout>
-
-            <ImageButton
-                android:id="@+id/now_playing_details_add_to_playlist"
-                style="@style/IconButton"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_margin="8dp"
-                android:contentDescription="@string/alt_album_cover"
-                android:src="@drawable/add_to_playlist" />
-
-            <ImageButton
-                android:id="@+id/now_playing_details_favorite"
-                style="@style/IconButton"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_margin="8dp"
-                android:contentDescription="@string/alt_album_cover"
-                android:src="@drawable/favorite" />
-
-            <ImageButton
-                android:id="@+id/now_playing_details_info"
-                style="@style/IconButton"
-                android:layout_width="24dp"
-                android:layout_height="24dp"
-                android:layout_margin="8dp"
-                android:contentDescription="@string/alt_track_info"
-                android:src="@drawable/more"
-                app:tint="@color/controlForeground" />
-
-        </LinearLayout>
-
-        <SeekBar
-            android:id="@+id/now_playing_details_progress"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="16dp"
-            android:max="100"
-            android:progressBackgroundTint="#cacaca"
-            android:progressTint="@color/controlForeground"
-            android:thumbOffset="3dp"
-            android:paddingStart="0dp"
-            android:paddingEnd="0dp"
-            android:thumbTint="@color/controlForeground" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <TextView
-                android:id="@+id/now_playing_details_progress_current"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_weight="1" />
-
-            <TextView
-                android:id="@+id/now_playing_details_progress_duration"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:textAlignment="textEnd" />
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="64dp"
-            android:layout_marginBottom="8dp"
-            android:gravity="center"
-            android:orientation="horizontal">
-
-            <ImageButton
-                android:id="@+id/now_playing_details_previous"
-                style="@style/IconButton"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_marginEnd="16dp"
-                android:contentDescription="@string/control_previous"
-                android:src="@drawable/previous" />
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/now_playing_details_toggle"
-                style="@style/AppTheme.OutlinedButton"
-                android:layout_width="64dp"
-                android:layout_height="64dp"
-                app:cornerRadius="64dp"
-                app:icon="@drawable/play"
-                app:iconSize="32dp" />
-
-            <ImageButton
-                android:id="@+id/now_playing_details_next"
-                style="@style/IconButton"
-                android:layout_width="48dp"
-                android:layout_height="48dp"
-                android:layout_marginStart="16dp"
-                android:contentDescription="@string/control_next"
-                android:src="@drawable/next" />
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="16dp"
-            android:gravity="center"
-            android:orientation="horizontal">
-
-            <ImageButton
-                android:id="@+id/now_playing_details_repeat"
-                style="@style/IconButton"
-                android:layout_width="28dp"
-                android:layout_height="28dp"
-                android:contentDescription="@string/control_next"
-                android:src="@drawable/repeat" />
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 246e22ade95276cadb9d1423ec380f76199d19dc..a732247f2db0ec04b396f041bf4b645df41d7df1 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,50 +1,65 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/surface">
+<androidx.constraintlayout.widget.ConstraintLayout
+  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:layout_width="match_parent"
+  android:layout_height="match_parent">
 
+  <androidx.coordinatorlayout.widget.CoordinatorLayout
+    android:layout_width="match_parent"
+    android:layout_height="0dp"
+    android:background="@color/surface"
+    app:layout_constraintVertical_weight="10"
+    app:layout_constraintTop_toTopOf="parent">
     <androidx.fragment.app.FragmentContainerView
-        android:id="@+id/nav_host_fragment"
-        android:name="androidx.navigation.fragment.NavHostFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginBottom="?attr/actionBarSize"
-        app:defaultNavHost="true"
-        app:navGraph="@navigation/main_nav"
-        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+      android:id="@+id/nav_host_fragment"
+      android:name="androidx.navigation.fragment.NavHostFragment"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      app:defaultNavHost="true"
+      app:layout_behavior="@string/appbar_scrolling_view_behavior"
+      app:navGraph="@navigation/main_nav"
+      tools:layout="@layout/fragment_artists" />
+  </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
-    <audio.funkwhale.ffa.views.NowPlayingView
+  <androidx.coordinatorlayout.widget.CoordinatorLayout
+    android:layout_width="match_parent"
+    android:layout_height="0dp"
+    app:layout_constraintTop_toTopOf="parent"
+    app:layout_constraintBottom_toTopOf="@id/appbar_wrapper">
+    <audio.funkwhale.ffa.views.NowPlayingBottomSheet
+      android:id="@+id/now_playing_bottom_sheet"
+      style="?attr/bottomSheetStyle"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      app:cardCornerRadius="3dp"
+      app:cardElevation="12dp"
+      app:target_header="@id/header">
+      <androidx.fragment.app.FragmentContainerView
         android:id="@+id/now_playing"
         android:layout_width="match_parent"
-        android:layout_height="?attr/actionBarSize"
-        android:layout_gravity="bottom"
-        android:layout_margin="8dp"
-        android:alpha="0"
-        android:visibility="gone"
-        app:cardCornerRadius="3dp"
-        app:cardElevation="12dp"
-        app:layout_dodgeInsetEdges="bottom"
-        tools:alpha="1"
-        tools:visibility="visible">
-
-        <include
-            android:id="@+id/now_playing_container"
-            layout="@layout/partial_now_playing" />
+        android:layout_height="match_parent"
+        android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment"
+        tools:layout="@layout/fragment_now_playing"/>
+    </audio.funkwhale.ffa.views.NowPlayingBottomSheet>
+  </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
-    </audio.funkwhale.ffa.views.NowPlayingView>
+  <androidx.coordinatorlayout.widget.CoordinatorLayout
+    android:id="@+id/appbar_wrapper"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    app:layout_constraintBottom_toBottomOf="parent">
 
     <com.google.android.material.bottomappbar.BottomAppBar
-        android:id="@+id/appbar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom"
-        android:theme="@style/AppTheme.AppBar"
-        app:backgroundTint="@color/elevatedSurface"
-        app:layout_insetEdge="bottom"
-        app:navigationIcon="@drawable/funkwhaleshape"
-        tools:menu="@menu/toolbar" />
-
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+      android:id="@+id/appbar"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_gravity="bottom"
+      android:theme="@style/AppTheme.AppBar"
+      app:backgroundTint="@color/elevatedSurface"
+      app:layout_insetEdge="bottom"
+      app:navigationIcon="@drawable/funkwhaleshape"
+      tools:menu="@menu/toolbar" />
+  </androidx.coordinatorlayout.widget.CoordinatorLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_now_playing.xml b/app/src/main/res/layout/fragment_now_playing.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e22e1d0f45ab54f17b80fd412d5e6c4198fc3083
--- /dev/null
+++ b/app/src/main/res/layout/fragment_now_playing.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  xmlns:tools="http://schemas.android.com/tools">
+  <androidx.constraintlayout.widget.ConstraintLayout
+    android:id="@+id/now_playing_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/elevatedSurface">
+    
+    <include
+      android:id="@+id/header"
+      layout="@layout/partial_now_playing_header" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+      android:id="@+id/cover_container"
+      android:layout_width="match_parent"
+      android:layout_height="0dp"
+      android:padding="8dp"
+      app:layout_constraintTop_toBottomOf="@id/header"
+      app:layout_constraintBottom_toTopOf="@id/controls">
+
+      <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/now_playing_detail_cover"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:scaleType="centerCrop"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:srcCompat="@drawable/cover"
+        tools:src="@tools:sample/avatars" />
+
+      <ImageButton
+        android:id="@+id/now_playing_details_info"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_margin="8dp"
+        app:layout_constraintEnd_toEndOf="@id/now_playing_detail_cover"
+        app:layout_constraintTop_toTopOf="@id/now_playing_detail_cover"
+        style="@style/IconButton"
+        android:layout_gravity="top|end"
+        android:background="@drawable/circle"
+        android:contentDescription="@string/alt_track_info"
+        android:src="@drawable/more"
+        app:tint="@color/controlForeground" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <include
+      android:id="@+id/controls"
+      layout="@layout/partial_now_playing_controls"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_margin="8dp"
+      app:layout_constraintBottom_toBottomOf="parent"
+    />
+  </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/partial_now_playing.xml b/app/src/main/res/layout/partial_now_playing.xml
deleted file mode 100644
index 560b7983e584a3a774f3ec4eddba4ee988042740..0000000000000000000000000000000000000000
--- a/app/src/main/res/layout/partial_now_playing.xml
+++ /dev/null
@@ -1,283 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout 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/now_playing_root"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="@color/elevatedSurface"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:id="@+id/summary"
-        android:layout_width="match_parent"
-        android:layout_height="?attr/actionBarSize"
-        android:orientation="vertical">
-
-        <ProgressBar
-            android:id="@+id/now_playing_progress"
-            style="@android:style/Widget.Material.ProgressBar.Horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="-6dp"
-            android:layout_marginBottom="-6dp"
-            android:progress="40"
-            android:progressTint="@color/colorPrimaryDark" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_vertical"
-            android:orientation="horizontal">
-
-            <FrameLayout
-                android:layout_width="?attr/actionBarSize"
-                android:layout_height="?attr/actionBarSize"
-                android:layout_marginEnd="16dp">
-
-                <audio.funkwhale.ffa.views.SquareImageView
-                    android:id="@+id/now_playing_cover"
-                    android:layout_width="?attr/actionBarSize"
-                    android:layout_height="?attr/actionBarSize"
-                    tools:src="@tools:sample/avatars" />
-
-                <ProgressBar
-                    android:id="@+id/now_playing_buffering"
-                    android:layout_width="?attr/actionBarSize"
-                    android:layout_height="?attr/actionBarSize"
-                    android:indeterminate="true"
-                    android:indeterminateTint="@color/controlForeground"
-                    android:visibility="gone" />
-
-            </FrameLayout>
-
-            <LinearLayout
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:layout_marginEnd="8dp"
-                android:layout_weight="2"
-                android:orientation="vertical">
-
-                <TextView
-                    android:id="@+id/now_playing_title"
-                    style="@style/AppTheme.ItemTitle"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:ellipsize="end"
-                    android:lines="1"
-                    tools:text="Supermassive Black Hole" />
-
-                <TextView
-                    android:id="@+id/now_playing_album"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:ellipsize="end"
-                    android:lines="1"
-                    tools:text="Muse" />
-
-            </LinearLayout>
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/now_playing_toggle"
-                style="@style/AppTheme.OutlinedButton"
-                android:layout_width="?attr/actionBarSize"
-                android:layout_height="match_parent"
-                android:layout_marginEnd="16dp"
-                app:icon="@drawable/play" />
-
-            <ImageButton
-                android:id="@+id/now_playing_next"
-                style="@style/IconButton"
-                android:layout_width="32dp"
-                android:layout_height="32dp"
-                android:layout_marginEnd="16dp"
-                android:contentDescription="@string/control_next"
-                android:src="@drawable/next" />
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/now_playing_details"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:padding="8dp">
-
-            <audio.funkwhale.ffa.views.SquareImageView
-                android:id="@+id/now_playing_details_cover"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:adjustViewBounds="true"
-                android:src="@drawable/funkwhaleshape"
-                tools:src="@tools:sample/avatars" />
-
-            <ImageButton
-                android:id="@+id/now_playing_details_info"
-                style="@style/IconButton"
-                android:layout_width="32dp"
-                android:layout_height="32dp"
-                android:layout_gravity="top|end"
-                android:layout_margin="8dp"
-                android:background="@drawable/circle"
-                android:contentDescription="@string/alt_track_info"
-                android:src="@drawable/more"
-                app:tint="@color/controlForeground" />
-
-        </FrameLayout>
-
-        <LinearLayout
-            android:id="@+id/now_playing_details_controls"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="32dp"
-            android:layout_marginEnd="32dp"
-            android:orientation="vertical"
-            android:paddingTop="16dp">
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal">
-
-                <LinearLayout
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:orientation="vertical">
-
-                    <TextView
-                        android:id="@+id/now_playing_details_title"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:textColor="@color/itemTitle"
-                        android:textSize="18sp"
-                        tools:text="Supermassive Black Hole" />
-
-                    <TextView
-                        android:id="@+id/now_playing_details_artist"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        tools:text="Muse" />
-
-                </LinearLayout>
-
-                <ImageButton
-                    android:id="@+id/now_playing_details_add_to_playlist"
-                    style="@style/IconButton"
-                    android:layout_width="24dp"
-                    android:layout_height="24dp"
-                    android:layout_margin="8dp"
-                    android:contentDescription="@string/playlist_add_to"
-                    android:src="@drawable/add_to_playlist" />
-
-                <ImageButton
-                    android:id="@+id/now_playing_details_favorite"
-                    style="@style/IconButton"
-                    android:layout_width="24dp"
-                    android:layout_height="24dp"
-                    android:layout_margin="8dp"
-                    android:contentDescription="@string/alt_album_cover"
-                    android:src="@drawable/favorite" />
-
-            </LinearLayout>
-
-            <SeekBar
-                android:id="@+id/now_playing_details_progress"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="16dp"
-                android:max="100"
-                android:paddingStart="0dp"
-                android:paddingEnd="0dp"
-                android:progressBackgroundTint="#cacaca"
-                android:progressTint="@color/controlForeground"
-                android:thumbOffset="3dp"
-                android:thumbTint="@color/controlForeground" />
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal">
-
-                <TextView
-                    android:id="@+id/now_playing_details_progress_current"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1" />
-
-                <TextView
-                    android:id="@+id/now_playing_details_progress_duration"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:textAlignment="textEnd" />
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="64dp"
-                android:layout_marginBottom="8dp"
-                android:gravity="center"
-                android:orientation="horizontal">
-
-                <ImageButton
-                    android:id="@+id/now_playing_details_previous"
-                    style="@style/IconButton"
-                    android:layout_width="32dp"
-                    android:layout_height="32dp"
-                    android:layout_marginEnd="16dp"
-                    android:contentDescription="@string/control_previous"
-                    android:src="@drawable/previous" />
-
-                <com.google.android.material.button.MaterialButton
-                    android:id="@+id/now_playing_details_toggle"
-                    style="@style/AppTheme.OutlinedButton"
-                    android:layout_width="64dp"
-                    android:layout_height="64dp"
-                    app:cornerRadius="64dp"
-                    app:icon="@drawable/play"
-                    app:iconSize="32dp" />
-
-                <ImageButton
-                    android:id="@+id/now_playing_details_next"
-                    style="@style/IconButton"
-                    android:layout_width="32dp"
-                    android:layout_height="32dp"
-                    android:layout_marginStart="16dp"
-                    android:contentDescription="@string/control_next"
-                    android:src="@drawable/next" />
-
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginBottom="16dp"
-                android:gravity="center"
-                android:orientation="horizontal">
-
-                <ImageButton
-                    android:id="@+id/now_playing_details_repeat"
-                    style="@style/IconButton"
-                    android:layout_width="28dp"
-                    android:layout_height="28dp"
-                    android:contentDescription="@string/control_next"
-                    android:src="@drawable/repeat" />
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/app/src/main/res/layout/partial_now_playing_controls.xml b/app/src/main/res/layout/partial_now_playing_controls.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4196c71f639e88233a41b373b63cc6b5182fde57
--- /dev/null
+++ b/app/src/main/res/layout/partial_now_playing_controls.xml
@@ -0,0 +1,159 @@
+<?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>
+    <import type="androidx.lifecycle.LiveData" />
+    <import type="android.graphics.drawable.Drawable" />
+    <variable name="currentTrackTitle" type="LiveData&lt;String>" />
+    <variable name="currentTrackArtist" type="LiveData&lt;String>" />
+    <variable name="isCurrentTrackFavorite" type="LiveData&lt;Boolean>" />
+    <variable name="repeatModeResource" type="LiveData&lt;Drawable>" />
+    <variable name="repeatModeAlpha" type="LiveData&lt;Float>" />
+    <variable name="currentProgressText" type="LiveData&lt;String>" />
+    <variable name="currentDurationText" type="LiveData&lt;String>" />
+    <variable name="isPlaying" type="LiveData&lt;Boolean>" />
+    <variable name="progress" type="LiveData&lt;Integer>" />
+  </data>
+  <androidx.constraintlayout.widget.ConstraintLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <TextView
+      android:id="@+id/current_playing_details_title"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      android:paddingTop="2dp"
+      android:paddingBottom="2dp"
+      android:text="@{currentTrackTitle}"
+      android:textColor="@color/itemTitle"
+      android:textSize="18sp"
+      app:layout_constraintEnd_toStartOf="@+id/now_playing_details_add_to_playlist"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toTopOf="parent" tools:text="Supermassive Black Hole" />
+
+    <TextView
+      android:id="@+id/current_playing_details_artist"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      android:paddingTop="2dp"
+      android:paddingBottom="2dp"
+      android:text="@{currentTrackArtist}"
+      app:layout_constraintEnd_toStartOf="@+id/now_playing_details_add_to_playlist"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/current_playing_details_title"
+      tools:text="Muse" />
+
+    <ImageButton
+      android:id="@+id/now_playing_details_add_to_playlist"
+      style="@style/IconButton"
+      android:layout_width="24dp"
+      android:layout_height="24dp"
+      android:layout_margin="8dp"
+      android:contentDescription="@string/playlist_add_to"
+      android:src="@drawable/add_to_playlist"
+      app:layout_constraintBottom_toBottomOf="@+id/current_playing_details_artist"
+
+      app:layout_constraintEnd_toStartOf="@+id/now_playing_details_favorite"
+      app:layout_constraintTop_toTopOf="@+id/current_playing_details_title" />
+
+    <ImageButton
+      android:id="@+id/now_playing_details_favorite"
+      style="@style/IconButton"
+      android:layout_width="24dp"
+      android:layout_height="24dp"
+      android:layout_margin="8dp"
+      android:contentDescription="@string/control_add_to_favorties"
+      android:src="@drawable/favorite"
+      app:layout_constraintBottom_toBottomOf="@+id/current_playing_details_artist"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintTop_toTopOf="@+id/current_playing_details_title"
+      app:tint="@{isCurrentTrackFavorite ? @color/colorFavorite : @color/controlForeground, default=@color/controlForeground}" />
+
+
+    <TextView
+      android:id="@+id/now_playing_details_progress_current"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text='@{currentProgressText, default="5:04"}'
+      app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_progress"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toTopOf="@+id/now_playing_details_progress" />
+
+    <SeekBar
+      android:id="@+id/now_playing_details_progress"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      android:max="100"
+      android:layout_margin="8dp"
+      android:progress="@{progress, default=40}"
+      android:progressBackgroundTint="#cacaca"
+      android:progressTint="@color/controlForeground"
+      android:thumbOffset="3dp"
+      android:thumbTint="@color/controlForeground"
+      app:layout_constraintEnd_toStartOf="@+id/now_playing_details_progress_duration"
+      app:layout_constraintStart_toEndOf="@+id/now_playing_details_progress_current"
+      app:layout_constraintTop_toBottomOf="@+id/current_playing_details_artist" />
+
+    <TextView
+      android:id="@+id/now_playing_details_progress_duration"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text='@{currentDurationText, default="5:04"}'
+      android:textAlignment="textEnd"
+      app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_progress"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintTop_toTopOf="@+id/now_playing_details_progress" />
+
+    <ImageButton
+      android:id="@+id/now_playing_details_previous"
+      style="@style/IconButton"
+      android:layout_width="32dp"
+      android:layout_height="32dp"
+      android:layout_margin="8dp"
+      android:contentDescription="@string/control_previous"
+      android:src="@drawable/previous"
+      app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_toggle"
+      app:layout_constraintEnd_toStartOf="@+id/now_playing_details_toggle"
+      app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress" />
+
+    <com.google.android.material.button.MaterialButton
+      android:id="@+id/now_playing_details_toggle"
+      style="@style/AppTheme.OutlinedButton"
+      android:layout_width="64dp"
+      android:layout_height="64dp"
+      android:layout_margin="8dp"
+      app:cornerRadius="64dp"
+      app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}"
+      app:iconSize="32dp"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress" />
+
+    <ImageButton
+      android:id="@+id/now_playing_details_next"
+      style="@style/IconButton"
+      android:layout_width="32dp"
+      android:layout_height="32dp"
+      android:layout_margin="8dp"
+      android:contentDescription="@string/control_next"
+      android:src="@drawable/next"
+      app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_toggle"
+      app:layout_constraintStart_toEndOf="@+id/now_playing_details_toggle"
+      app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress" />
+
+    <ImageButton
+      android:id="@+id/now_playing_details_repeat"
+      style="@style/IconButton"
+      android:layout_width="32dp"
+      android:layout_height="32dp"
+      android:layout_margin="8dp"
+      android:alpha="@{repeatModeAlpha, default=1}"
+      android:contentDescription="@string/control_repeat_mode"
+      android:src="@{repeatModeResource, default=@drawable/repeat}"
+      app:layout_constraintBottom_toBottomOf="@+id/now_playing_details_toggle"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/now_playing_details_progress"
+      app:tint="@color/controlForeground" />
+  </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/partial_now_playing_header.xml b/app/src/main/res/layout/partial_now_playing_header.xml
new file mode 100644
index 0000000000000000000000000000000000000000..960c43f82d07f0236b9b31dfd55b61cd83270cce
--- /dev/null
+++ b/app/src/main/res/layout/partial_now_playing_header.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:tools="http://schemas.android.com/tools"
+  xmlns:app="http://schemas.android.com/apk/res-auto">
+  <data>
+    <import type="androidx.lifecycle.LiveData" />
+    <import type="android.view.View" />
+    <import type="android.graphics.drawable.Drawable" />
+    <variable name="isBuffering" type="LiveData&lt;Boolean>" />
+    <variable name="isPlaying" type="LiveData&lt;Boolean>" />
+    <variable name="progress" type="LiveData&lt;Integer>" />
+    <variable name="currentTrackTitle" type="LiveData&lt;String>" />
+    <variable name="currentTrackArtist" type="LiveData&lt;String>" />
+  </data>
+
+  <androidx.constraintlayout.widget.ConstraintLayout
+    android:layout_width="match_parent"
+    android:layout_height="?attr/actionBarSize">
+
+    <com.google.android.material.progressindicator.LinearProgressIndicator
+      android:id="@+id/now_playing_progress"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      app:layout_constraintTop_toTopOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintEnd_toEndOf="parent"
+      android:progress="@{progress, default=40}"
+      android:progressTint="@color/colorPrimaryDark" />
+
+    <audio.funkwhale.ffa.views.SquareImageView
+      android:id="@+id/now_playing_cover"
+      android:layout_width="0dp"
+      android:layout_height="0dp"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/now_playing_progress"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:srcCompat="@drawable/cover"
+      tools:src="@tools:sample/avatars" />
+
+    <ProgressBar
+      android:id="@+id/now_playing_buffering"
+      android:layout_width="0dp"
+      android:layout_height="0dp"
+      app:layout_constraintStart_toStartOf="@id/now_playing_cover"
+      app:layout_constraintTop_toTopOf="@id/now_playing_cover"
+      app:layout_constraintBottom_toBottomOf="@id/now_playing_cover"
+      app:layout_constraintEnd_toEndOf="@id/now_playing_cover"
+      android:indeterminate="true"
+      android:indeterminateTint="@color/controlForeground"
+      android:visibility="@{isBuffering ? View.VISIBLE : View.GONE, default=gone}" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+      android:id="@+id/header_controls"
+      android:layout_width="0dp"
+      android:layout_height="0dp"
+      app:layout_constraintHorizontal_weight="10"
+      app:layout_constraintStart_toEndOf="@id/now_playing_cover"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
+      app:layout_constraintBottom_toBottomOf="parent"
+      android:padding="4dp">
+
+      <TextView
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/now_playing_toggle"
+        style="@style/AppTheme.ItemTitle"
+        android:text="@{currentTrackTitle}"
+        android:ellipsize="end"
+        android:lines="1"
+        tools:text="Supermassive Black Hole" />
+
+      <TextView
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/now_playing_toggle"
+        android:ellipsize="end"
+        android:lines="1"
+        android:text="@{currentTrackArtist}"
+        tools:text="Muse" />
+
+      <com.google.android.material.button.MaterialButton
+        android:id="@+id/now_playing_toggle"
+        style="@style/AppTheme.OutlinedButton"
+        android:layout_width="?attr/actionBarSize"
+        android:layout_height="match_parent"
+        app:layout_constraintEnd_toStartOf="@id/now_playing_next"
+        android:layout_marginEnd="16dp"
+        app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}" />
+
+      <ImageButton
+        android:id="@+id/now_playing_next"
+        android:layout_width="32dp"
+        android:layout_height="match_parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        style="@style/IconButton"
+        android:contentDescription="@string/control_next"
+        android:src="@drawable/next" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+  </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>
\ No newline at end of file
diff --git a/app/src/main/res/navigation/main_nav.xml b/app/src/main/res/navigation/main_nav.xml
index 6d7915004c20971d1717dcf2484524cbd35efa49..1869352e282f7ac05fac7a7c0fa77ed8a7628659 100644
--- a/app/src/main/res/navigation/main_nav.xml
+++ b/app/src/main/res/navigation/main_nav.xml
@@ -1,103 +1,119 @@
 <?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/main_nav"
-    app:startDestination="@id/browseFragment">
+            xmlns:app="http://schemas.android.com/apk/res-auto"
+            xmlns:tools="http://schemas.android.com/tools"
+            android:id="@+id/main_nav"
+            app:startDestination="@id/browseFragment">
 
-    <fragment
-        android:id="@+id/browseFragment"
-        android:name="audio.funkwhale.ffa.fragments.BrowseFragment"
-        android:label="BrowseFragment">
-        <action
-            android:id="@+id/browseToSearch"
-            app:destination="@id/searchFragment"
-            app:enterAnim="@anim/slide_up"
-            app:exitAnim="@anim/delayed_fade_out"
-            app:popEnterAnim="@anim/none"
-            app:popExitAnim="@anim/slide_down" />
-        <action
-            android:id="@+id/browseToAlbums"
-            app:destination="@id/albumsFragment"
-            app:enterAnim="@anim/slide_up"
-            app:exitAnim="@anim/delayed_fade_out"
-            app:popEnterAnim="@anim/none"
-            app:popExitAnim="@anim/slide_down" />
-        <action
-            android:id="@+id/browseToTracks"
-            app:destination="@id/tracksFragment"
-            app:enterAnim="@anim/slide_up"
-            app:exitAnim="@anim/delayed_fade_out"
-            app:popEnterAnim="@anim/none"
-            app:popExitAnim="@anim/slide_down" />
-        <action
-            android:id="@+id/browseToArtists"
-            app:destination="@id/artistsFragment"
-            app:enterAnim="@anim/slide_up"
-            app:exitAnim="@anim/delayed_fade_out"
-            app:popEnterAnim="@anim/none"
-            app:popExitAnim="@anim/slide_down" />
-        <action
-            android:id="@+id/browseToPlaylistTracks"
-            app:destination="@id/playlistTracksFragment"
-            app:enterAnim="@anim/slide_up"
-            app:exitAnim="@anim/delayed_fade_out"
-            app:popEnterAnim="@anim/none"
-            app:popExitAnim="@anim/slide_down" />
-    </fragment>
-    <fragment
-        android:id="@+id/playlistTracksFragment"
-        android:name="audio.funkwhale.ffa.fragments.PlaylistTracksFragment"
-        android:label="PlaylistTracksFragment" >
-        <argument
-            android:name="playlist"
-            app:argType="audio.funkwhale.ffa.model.Playlist" />
-    </fragment>
-    <fragment
-        android:id="@+id/tracksFragment"
-        android:name="audio.funkwhale.ffa.fragments.TracksFragment"
-        android:label="TracksFragment" >
-        <argument
-            android:name="album"
-            app:argType="audio.funkwhale.ffa.model.Album" />
-    </fragment>
-    <fragment
-        android:id="@+id/albumsFragment"
-        android:name="audio.funkwhale.ffa.fragments.AlbumsFragment"
-        android:label="AlbumsFragment" >
-        <argument
-            android:name="artist"
-            app:argType="audio.funkwhale.ffa.model.Artist" />
-        <argument
-            android:name="cover"
-            app:argType="string"
-            app:nullable="true"
-            android:defaultValue="@null" />
-        <action
-            android:id="@+id/albumsToTracks"
-            app:destination="@id/tracksFragment" />
-    </fragment>
-    <fragment
-        android:id="@+id/searchFragment"
-        android:name="audio.funkwhale.ffa.fragments.SearchFragment"
-        android:label="SearchFragment" >
-        <action
-            android:id="@+id/searchToAlbums"
-            app:destination="@id/albumsFragment"
-            app:enterAnim="@anim/slide_up"
-            app:exitAnim="@anim/delayed_fade_out"
-            app:popEnterAnim="@anim/none"
-            app:popExitAnim="@anim/slide_down" />
-        <action
-            android:id="@+id/searchToTracks"
-            app:destination="@id/tracksFragment"
-            app:enterAnim="@anim/slide_up"
-            app:exitAnim="@anim/delayed_fade_out"
-            app:popEnterAnim="@anim/none"
-            app:popExitAnim="@anim/slide_down" />
-    </fragment>
-    <fragment
-        android:id="@+id/artistsFragment"
-        android:name="audio.funkwhale.ffa.fragments.ArtistsFragment"
-        android:label="ArtistsFragment" />
+  <fragment
+    android:id="@+id/browseFragment"
+    android:name="audio.funkwhale.ffa.fragments.BrowseFragment"
+    android:label="BrowseFragment">
+    <action
+      android:id="@+id/browseToSearch"
+      app:destination="@id/searchFragment"
+      app:enterAnim="@anim/slide_up"
+      app:exitAnim="@anim/delayed_fade_out"
+      app:popEnterAnim="@anim/none"
+      app:popExitAnim="@anim/slide_down" />
+    <action
+      android:id="@+id/browseToAlbums"
+      app:destination="@id/albumsFragment"
+      app:enterAnim="@anim/slide_up"
+      app:exitAnim="@anim/delayed_fade_out"
+      app:popEnterAnim="@anim/none"
+      app:popExitAnim="@anim/slide_down" />
+    <action
+      android:id="@+id/browseToTracks"
+      app:destination="@id/tracksFragment"
+      app:enterAnim="@anim/slide_up"
+      app:exitAnim="@anim/delayed_fade_out"
+      app:popEnterAnim="@anim/none"
+      app:popExitAnim="@anim/slide_down" />
+    <action
+      android:id="@+id/browseToArtists"
+      app:destination="@id/artistsFragment"
+      app:enterAnim="@anim/slide_up"
+      app:exitAnim="@anim/delayed_fade_out"
+      app:popEnterAnim="@anim/none"
+      app:popExitAnim="@anim/slide_down" />
+    <action
+      android:id="@+id/browseToPlaylistTracks"
+      app:destination="@id/playlistTracksFragment"
+      app:enterAnim="@anim/slide_up"
+      app:exitAnim="@anim/delayed_fade_out"
+      app:popEnterAnim="@anim/none"
+      app:popExitAnim="@anim/slide_down" />
+  </fragment>
+  <fragment
+    android:id="@+id/playlistTracksFragment"
+    android:name="audio.funkwhale.ffa.fragments.PlaylistTracksFragment"
+    android:label="PlaylistTracksFragment">
+    <argument
+      android:name="playlist"
+      app:argType="audio.funkwhale.ffa.model.Playlist" />
+  </fragment>
+  <fragment
+    android:id="@+id/tracksFragment"
+    android:name="audio.funkwhale.ffa.fragments.TracksFragment"
+    android:label="TracksFragment">
+    <argument
+      android:name="album"
+      app:argType="audio.funkwhale.ffa.model.Album" />
+  </fragment>
+  <fragment
+    android:id="@+id/albumsFragment"
+    android:name="audio.funkwhale.ffa.fragments.AlbumsFragment"
+    android:label="AlbumsFragment">
+    <argument
+      android:name="artist"
+      app:argType="audio.funkwhale.ffa.model.Artist" />
+    <argument
+      android:name="cover"
+      android:defaultValue="@null"
+      app:argType="string"
+      app:nullable="true" />
+    <action
+      android:id="@+id/albumsToTracks"
+      app:destination="@id/tracksFragment" />
+  </fragment>
+  <fragment
+    android:id="@+id/searchFragment"
+    android:name="audio.funkwhale.ffa.fragments.SearchFragment"
+    android:label="SearchFragment">
+    <action
+      android:id="@+id/searchToAlbums"
+      app:destination="@id/albumsFragment"
+      app:enterAnim="@anim/slide_up"
+      app:exitAnim="@anim/delayed_fade_out"
+      app:popEnterAnim="@anim/none"
+      app:popExitAnim="@anim/slide_down" />
+    <action
+      android:id="@+id/searchToTracks"
+      app:destination="@id/tracksFragment"
+      app:enterAnim="@anim/slide_up"
+      app:exitAnim="@anim/delayed_fade_out"
+      app:popEnterAnim="@anim/none"
+      app:popExitAnim="@anim/slide_down" />
+  </fragment>
+  <fragment
+    android:id="@+id/artistsFragment"
+    android:name="audio.funkwhale.ffa.fragments.ArtistsFragment"
+    android:label="ArtistsFragment" />
+  <action
+    android:id="@+id/globalBrowseToAlbums"
+    app:destination="@id/albumsFragment"
+    app:enterAnim="@anim/slide_up"
+    app:exitAnim="@anim/delayed_fade_out"
+    app:popEnterAnim="@anim/none"
+    app:popExitAnim="@anim/slide_down"
+  />
+  <action
+    android:id="@+id/globalBrowseTracks"
+    app:destination="@id/tracksFragment"
+    app:enterAnim="@anim/slide_up"
+    app:exitAnim="@anim/delayed_fade_out"
+    app:popEnterAnim="@anim/none"
+    app:popExitAnim="@anim/slide_down"
+  />
 </navigation>
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2a5fe063083a40cdb81e723b676b5cc9463437ee
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <declare-styleable name="NowPlaying">
+    <attr name="target_header" format="reference" />
+  </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b99a05d354672f0de0764e317d61d36749c33ede..a6c42cc699d57cf6723ad8f19fc2695dc30e6b47 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -76,6 +76,8 @@
   <string name="control_toggle">Toggle playback</string>
   <string name="control_previous">Previous track</string>
   <string name="control_next">Next track</string>
+  <string name="control_repeat_mode">Repeat mode</string>
+  <string name="control_add_to_favorties">Add to favorties</string>
   <string name="error_playback">This track could not be played</string>
   <plurals name="album_count">
     <item quantity="one">%d album</item>