diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c0d7898..58c5bd8 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 62f7deb..9bfd708 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">
-
@@ -40,9 +38,7 @@
android:launchMode="singleInstance"
android:screenOrientation="portrait" />
-
+
(R.id.container)?.apply {
- setShouldRegisterTouch {
- if (binding.nowPlaying.isOpened()) {
- binding.nowPlaying.close()
- false
- } else {
- true
- }
- }
- }
+ binding.nowPlaying.getFragment().apply {
+ favoritedRepository.update(requireContext(), lifecycleScope)
- favoritedRepository.update(this, lifecycleScope)
+ startService(Intent(requireContext(), PlayerService::class.java))
+ DownloadService.start(requireContext(), PinService::class.java)
- startService(Intent(this, PlayerService::class.java))
- DownloadService.start(this, PinService::class.java)
+ CommandBus.send(Command.RefreshService)
- CommandBus.send(Command.RefreshService)
-
- lifecycleScope.launch(IO) {
- Userinfo.get(this@MainActivity, oAuth)
- }
-
- with(binding) {
-
- nowPlayingContainer?.nowPlayingToggle?.setOnClickListener {
- CommandBus.send(Command.ToggleState)
- }
-
- nowPlayingContainer?.nowPlayingNext?.setOnClickListener {
- CommandBus.send(Command.NextTrack)
- }
-
- 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(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(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 2e00174..e376617 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 b8cb5ba..65caed3 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()
-
+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 0000000..539d652
--- /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()
+ 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) {
+ 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 0000000..f864591
--- /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 3cd73d9..85b137f 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 9a81a6d..2e53712 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 LiveData.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 0000000..6a12db7
--- /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 0000000..4a972ae
--- /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