Fix overlap between main fragment and player bottom bar

This commit is contained in:
Christophe Henry 2023-04-20 10:47:23 +02:00
parent fbbd90111d
commit 822adcac4a
4 changed files with 89 additions and 107 deletions

View File

@ -1,30 +1,24 @@
package audio.funkwhale.ffa.activities package audio.funkwhale.ffa.activities
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.SeekBar
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
@ -33,54 +27,41 @@ import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.ActivityMainBinding import audio.funkwhale.ffa.databinding.ActivityMainBinding
import audio.funkwhale.ffa.fragments.AddToPlaylistDialog import audio.funkwhale.ffa.fragments.AddToPlaylistDialog
import audio.funkwhale.ffa.fragments.BrowseFragmentDirections import audio.funkwhale.ffa.fragments.BrowseFragmentDirections
import audio.funkwhale.ffa.fragments.LandscapeQueueFragment
import audio.funkwhale.ffa.fragments.NowPlayingFragment import audio.funkwhale.ffa.fragments.NowPlayingFragment
import audio.funkwhale.ffa.fragments.QueueFragment import audio.funkwhale.ffa.fragments.QueueFragment
import audio.funkwhale.ffa.fragments.TrackInfoDetailsFragment
import audio.funkwhale.ffa.model.Track import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.playback.MediaControlsManager import audio.funkwhale.ffa.playback.MediaControlsManager
import audio.funkwhale.ffa.playback.PinService import audio.funkwhale.ffa.playback.PinService
import audio.funkwhale.ffa.playback.PlayerService import audio.funkwhale.ffa.playback.PlayerService
import audio.funkwhale.ffa.repositories.FavoritedRepository import audio.funkwhale.ffa.repositories.FavoritedRepository
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.repositories.Repository
import audio.funkwhale.ffa.utils.AppContext import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.Command import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.Event import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.OAuth import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.ProgressBus
import audio.funkwhale.ffa.utils.Settings import audio.funkwhale.ffa.utils.Settings
import audio.funkwhale.ffa.utils.Userinfo import audio.funkwhale.ffa.utils.Userinfo
import audio.funkwhale.ffa.utils.authorize import audio.funkwhale.ffa.utils.authorize
import audio.funkwhale.ffa.utils.log import audio.funkwhale.ffa.utils.log
import audio.funkwhale.ffa.utils.logError import audio.funkwhale.ffa.utils.logError
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.mustNormalizeUrl import audio.funkwhale.ffa.utils.mustNormalizeUrl
import audio.funkwhale.ffa.utils.onApi import audio.funkwhale.ffa.utils.onApi
import audio.funkwhale.ffa.utils.toast import audio.funkwhale.ffa.utils.toast
import audio.funkwhale.ffa.utils.untilNetwork
import audio.funkwhale.ffa.views.DisableableFrameLayout
import com.github.kittinunf.fuel.Fuel import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitStringResponse import com.github.kittinunf.fuel.coroutines.awaitStringResponse
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.offline.DownloadService import com.google.android.exoplayer2.offline.DownloadService
import com.google.android.material.bottomsheet.BottomSheetBehavior 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.google.gson.Gson
import com.preference.PowerPreference import com.preference.PowerPreference
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.java.KoinJavaComponent.inject import org.koin.java.KoinJavaComponent.inject
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
enum class ResultCode(val code: Int) { enum class ResultCode(val code: Int) {
LOGOUT(1001) LOGOUT(1001)
@ -102,6 +83,32 @@ class MainActivity : AppCompatActivity() {
AppContext.init(this) AppContext.init(this)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
binding.nowPlayingBottomSheet.hide()
binding.nowPlayingBottomSheet.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
// Set the proper margin on the other child
val anim = if (newState == BottomSheetBehavior.STATE_HIDDEN) {
ValueAnimator.ofInt(binding.nowPlayingBottomSheet.peekHeight, 0)
} else {
ValueAnimator.ofInt(0, binding.nowPlayingBottomSheet.peekHeight)
}
anim.apply {
duration = 200
addUpdateListener {
val params =
binding.navHostFragmentWrapper.layoutParams as CoordinatorLayout.LayoutParams
params.setMargins(0, 0, 0, it.animatedValue as Int)
}
start()
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
}
)
setContentView(binding.root) setContentView(binding.root)
setSupportActionBar(binding.appbar) setSupportActionBar(binding.appbar)
@ -125,10 +132,10 @@ class MainActivity : AppCompatActivity() {
super.onResume() super.onResume()
binding.nowPlaying.getFragment<NowPlayingFragment>().apply { binding.nowPlaying.getFragment<NowPlayingFragment>().apply {
favoritedRepository.update(requireContext(), lifecycleScope) favoritedRepository.update(this@MainActivity, lifecycleScope)
startService(Intent(requireContext(), PlayerService::class.java)) startService(Intent(this@MainActivity, PlayerService::class.java))
DownloadService.start(requireContext(), PinService::class.java) DownloadService.start(this@MainActivity, PinService::class.java)
CommandBus.send(Command.RefreshService) CommandBus.send(Command.RefreshService)
@ -237,6 +244,7 @@ class MainActivity : AppCompatActivity() {
return false return false
} }
} }
R.id.nav_downloads -> startActivity(Intent(this, DownloadsActivity::class.java)) R.id.nav_downloads -> startActivity(Intent(this, DownloadsActivity::class.java))
R.id.settings -> resultLauncher.launch(Intent(this, SettingsActivity::class.java)) R.id.settings -> resultLauncher.launch(Intent(this, SettingsActivity::class.java))
} }
@ -251,13 +259,13 @@ class MainActivity : AppCompatActivity() {
private fun watchEventBus() { private fun watchEventBus() {
lifecycleScope.launch(Main) { lifecycleScope.launch(Main) {
EventBus.get().collect { event -> EventBus.get().collect { event ->
when(event) { when (event) {
is Event.LogOut -> logout() is Event.LogOut -> logout()
is Event.PlaybackError -> toast(event.message) is Event.PlaybackError -> toast(event.message)
is Event.PlaybackStopped -> binding.nowPlayingBottomSheet.hide() is Event.PlaybackStopped -> binding.nowPlayingBottomSheet.hide()
is Event.TrackFinished -> incrementListenCount(event.track) is Event.TrackFinished -> incrementListenCount(event.track)
is Event.QueueChanged -> { is Event.QueueChanged -> {
if(binding.nowPlayingBottomSheet.isHidden) binding.nowPlayingBottomSheet.show() if (binding.nowPlayingBottomSheet.isHidden) binding.nowPlayingBottomSheet.show()
findViewById<View>(R.id.nav_queue)?.let { view -> findViewById<View>(R.id.nav_queue)?.let { view ->
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).let { ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).let {
it.duration = 500 it.duration = 500
@ -266,71 +274,41 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
else -> {} else -> {}
} }
} }
} }
lifecycleScope.launch(Main) { lifecycleScope.launch(Main) {
CommandBus.get().collect { command -> CommandBus.get().flowWithLifecycle(
if (command is Command.StartService) { this@MainActivity.lifecycle, Lifecycle.State.RESUMED
Build.VERSION_CODES.O.onApi( ).collect { command ->
{ when(command) {
startForegroundService( is Command.StartService -> startService(command.command)
Intent( is Command.RefreshTrack -> refreshTrack(command.track)
this@MainActivity, is Command.AddToPlaylist -> AddToPlaylistDialog.show(
PlayerService::class.java layoutInflater,
).apply { this@MainActivity,
putExtra(PlayerService.INITIAL_COMMAND_KEY, command.command.toString()) lifecycleScope,
} command.tracks
)
},
{
startService(
Intent(this@MainActivity, PlayerService::class.java).apply {
putExtra(PlayerService.INITIAL_COMMAND_KEY, command.command.toString())
}
)
}
) )
} else if (command is Command.RefreshTrack) { else -> {}
refreshCurrentTrack(command.track)
} else if (command is Command.AddToPlaylist) {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
AddToPlaylistDialog.show(
layoutInflater,
this@MainActivity,
lifecycleScope,
command.tracks
)
}
} }
} }
} }
} }
private fun refreshCurrentTrack(track: Track?) { private fun startService(command: Command) {
track?.let { val intent = Intent(this@MainActivity, PlayerService::class.java).apply {
if (binding.nowPlaying.visibility == View.GONE) { putExtra(PlayerService.INITIAL_COMMAND_KEY, command.toString())
binding.nowPlaying.visibility = View.VISIBLE }
binding.nowPlaying.alpha = 0f ContextCompat.startForegroundService(this, intent)
}
binding.nowPlaying.animate() private fun refreshTrack(track: Track?) {
.alpha(1.0f) if (track != null) {
.setDuration(400) binding.nowPlayingBottomSheet.show()
.setListener(null)
.start()
(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
}
}
}
} }
} }

View File

@ -5,27 +5,27 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.res.use
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.utils.BottomSheetIneractable import audio.funkwhale.ffa.utils.BottomSheetIneractable
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import androidx.core.content.res.use
class NowPlayingBottomSheet @JvmOverloads constructor(
class NowPlayingBottomSheet @JvmOverloads constructor (
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MaterialCardView(context, attrs), BottomSheetIneractable { ) : MaterialCardView(context, attrs), BottomSheetIneractable {
val behavior = BottomSheetBehavior<NowPlayingBottomSheet>(context, attrs) private val behavior = BottomSheetBehavior<NowPlayingBottomSheet>()
private val targetHeaderId: Int private val targetHeaderId: Int
val peekHeight get() = behavior.peekHeight
init { init {
targetHeaderId =context.theme.obtainStyledAttributes( targetHeaderId = context.theme.obtainStyledAttributes(
attrs, R.styleable.NowPlaying, defStyleAttr, 0 attrs, R.styleable.NowPlaying, defStyleAttr, 0
).use { ).use {
it.getResourceId(R.styleable.NowPlaying_target_header, NO_ID) it.getResourceId(R.styleable.NowPlaying_target_header, NO_ID)
} }
} }
@ -42,6 +42,10 @@ class NowPlayingBottomSheet @JvmOverloads constructor (
} ?: hide() } ?: hide()
} }
fun addBottomSheetCallback(callback: BottomSheetCallback) {
behavior.addBottomSheetCallback(callback)
}
// Bottom sheet interactions // Bottom sheet interactions
override val isHidden: Boolean get() = behavior.state == BottomSheetBehavior.STATE_HIDDEN override val isHidden: Boolean get() = behavior.state == BottomSheetBehavior.STATE_HIDDEN

View File

@ -13,9 +13,9 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_weight="10"> app:layout_constraintVertical_weight="10">
<LinearLayout <LinearLayout
android:id="@+id/nav_host_fragment_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal"> android:orientation="horizontal">
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView

View File

@ -6,28 +6,28 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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"
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>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/appbar_wrapper"> app:layout_constraintBottom_toTopOf="@id/appbar_wrapper">
<FrameLayout
android:id="@+id/nav_host_fragment_wrapper"
android:layout_width="match_parent"
android:layout_height="match_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"
app:defaultNavHost="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:navGraph="@navigation/main_nav"
tools:layout="@layout/fragment_artists"
/>
</FrameLayout>
<audio.funkwhale.ffa.views.NowPlayingBottomSheet <audio.funkwhale.ffa.views.NowPlayingBottomSheet
android:id="@+id/now_playing_bottom_sheet" android:id="@+id/now_playing_bottom_sheet"
style="?attr/bottomSheetStyle" style="?attr/bottomSheetStyle"