diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 58c5bd8..d8156e0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -181,7 +181,7 @@ dependencies { implementation("androidx.recyclerview:recyclerview:1.2.1") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.google.android.material:material:1.8.0") - implementation("com.android.support.constraint:constraint-layout:2.0.4") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("com.google.android.exoplayer:exoplayer-core:2.18.1") implementation("com.google.android.exoplayer:exoplayer-ui:2.18.1") 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 4d9887a..585dd24 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt @@ -3,6 +3,7 @@ package audio.funkwhale.ffa.activities import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.annotation.SuppressLint +import android.app.Fragment import android.content.Intent import android.os.Build import android.os.Bundle @@ -49,7 +50,6 @@ import audio.funkwhale.ffa.utils.authorize import audio.funkwhale.ffa.utils.log import audio.funkwhale.ffa.utils.logError import audio.funkwhale.ffa.utils.mustNormalizeUrl -import audio.funkwhale.ffa.utils.onApi import audio.funkwhale.ffa.utils.toast import audio.funkwhale.ffa.utils.wait import com.github.kittinunf.fuel.Fuel @@ -60,10 +60,6 @@ import com.google.gson.Gson import com.preference.PowerPreference import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.channels.consume -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.launch import org.koin.java.KoinJavaComponent.inject @@ -87,32 +83,28 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AppContext.init(this) + binding = ActivityMainBinding.inflate(layoutInflater) - 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) + (supportFragmentManager.findFragmentById(R.id.now_playing) as NowPlayingFragment).apply { + onDetailsMenuItemClicked { binding.nowPlayingBottomSheet.close() } + binding.nowPlayingBottomSheet.addBottomSheetCallback( + object : BottomSheetBehavior.BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + // Add padding to the main fragment so that player control don't overlap + // artists and albums + addSiblingFragmentPadding() } - 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) { + // Animate the cover and other elements of the bottom sheet + onBottomSheetDrag(slideOffset) } } + ) + } - override fun onSlide(bottomSheet: View, slideOffset: Float) {} - } - ) + addSiblingFragmentPadding() setContentView(binding.root) @@ -132,8 +124,11 @@ class MainActivity : AppCompatActivity() { lifecycleScope.launch { RequestBus.send(Request.GetQueue).wait()?.let { - if(it.queue.isNotEmpty()) binding.nowPlayingBottomSheet.show() - else binding.nowPlayingBottomSheet.hide() + if (it.queue.isNotEmpty() && binding.nowPlayingBottomSheet.isHidden) { + binding.nowPlayingBottomSheet.show() + } else if (it.queue.isEmpty()) { + binding.nowPlayingBottomSheet.hide() + } } // Watch the event bus only after to prevent concurrency in displaying the bottom sheet watchEventBus() @@ -265,6 +260,20 @@ class MainActivity : AppCompatActivity() { return true } + private fun addSiblingFragmentPadding() { + val anim = if (binding.nowPlayingBottomSheet.isHidden) { + ValueAnimator.ofInt(binding.nowPlayingBottomSheet.peekHeight, 0) + } else { + ValueAnimator.ofInt(0, binding.nowPlayingBottomSheet.peekHeight) + } + + anim.duration = 200 + anim.addUpdateListener { + binding.navHostFragmentWrapper.setPadding(0, 0, 0, it.animatedValue as Int) + } + anim.start() + } + private fun launchDialog(fragment: DialogFragment) = fragment.show(supportFragmentManager.beginTransaction(), "") @@ -297,7 +306,7 @@ class MainActivity : AppCompatActivity() { CommandBus.get().flowWithLifecycle( this@MainActivity.lifecycle, Lifecycle.State.RESUMED ).collect { command -> - when(command) { + when (command) { is Command.StartService -> startService(command.command) is Command.RefreshTrack -> refreshTrack(command.track) is Command.AddToPlaylist -> AddToPlaylistDialog.show( @@ -306,6 +315,7 @@ class MainActivity : AppCompatActivity() { lifecycleScope, command.tracks ) + else -> {} } } diff --git a/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt b/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt index b2fae8d..8973a9a 100644 --- a/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt +++ b/app/src/main/java/audio/funkwhale/ffa/fragments/NowPlayingFragment.kt @@ -18,7 +18,6 @@ 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.BottomSheetIneractable import audio.funkwhale.ffa.utils.Command import audio.funkwhale.ffa.utils.CommandBus import audio.funkwhale.ffa.utils.CoverArt @@ -30,24 +29,17 @@ import audio.funkwhale.ffa.utils.maybeNormalizeUrl import audio.funkwhale.ffa.utils.toIntOrElse import audio.funkwhale.ffa.utils.untilNetwork import audio.funkwhale.ffa.viewmodel.NowPlayingViewModel -import jp.wasabeef.picasso.transformations.RoundedCornersTransformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.lang.Float.max -class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) { +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 - } + private var onDetailsMenuItemClickedCb: () -> Unit = {} override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.lifecycleOwner = viewLifecycleOwner @@ -83,7 +75,10 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) { nowPlayingDetailsAddToPlaylist.setOnClickListener { onAddToPlaylist() } } + binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() } + with(binding.header) { + lifecycleOwner = viewLifecycleOwner isBuffering = viewModel.isBuffering isPlaying = viewModel.isPlaying progress = viewModel.progress @@ -100,8 +95,6 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) { } } - binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() } - lifecycleScope.launch(Dispatchers.Main) { CommandBus.get().collect { onCommand(it) } } @@ -115,6 +108,14 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) { } } + fun onBottomSheetDrag(value: Float) { + binding.nowPlayingRoot.progress = max(value, 0f) + } + + fun onDetailsMenuItemClicked(cb: () -> Unit) { + onDetailsMenuItemClickedCb = cb + } + private fun toggleRepeatMode() { val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0) @@ -170,19 +171,10 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) { 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) } @@ -199,7 +191,7 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) { inflate(R.menu.track_info) setOnMenuItemClickListener { - bottomSheet?.close() + onDetailsMenuItemClickedCb() when (it.itemId) { R.id.track_info_artist -> findNavController().navigate( diff --git a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt index 9144573..223631d 100644 --- a/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt +++ b/app/src/main/java/audio/funkwhale/ffa/views/NowPlayingBottomSheet.kt @@ -2,20 +2,21 @@ package audio.funkwhale.ffa.views import android.content.Context import android.util.AttributeSet +import android.util.TypedValue import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.res.use import audio.funkwhale.ffa.R import audio.funkwhale.ffa.utils.BottomSheetIneractable import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback -import com.google.android.material.card.MaterialCardView class NowPlayingBottomSheet @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : MaterialCardView(context, attrs), BottomSheetIneractable { +) : FrameLayout(context, attrs, defStyleAttr), BottomSheetIneractable { private val behavior = BottomSheetBehavior() private val targetHeaderId: Int @@ -27,6 +28,14 @@ class NowPlayingBottomSheet @JvmOverloads constructor( ).use { it.getResourceId(R.styleable.NowPlaying_target_header, NO_ID) } + + // Put default peek height to actionBarSize so it is not 0 + val tv = TypedValue() + if (context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) { + behavior.peekHeight = TypedValue.complexToDimensionPixelSize( + tv.data, resources.displayMetrics + ) + } } override fun setLayoutParams(params: ViewGroup.LayoutParams?) { @@ -37,7 +46,7 @@ class NowPlayingBottomSheet @JvmOverloads constructor( override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) findViewById(targetHeaderId)?.apply { - behavior.setPeekHeight(this.measuredHeight, false) + behavior.setPeekHeight(this.height, false) this.setOnClickListener { this@NowPlayingBottomSheet.toggle() } } ?: hide() } diff --git a/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt b/app/src/main/java/audio/funkwhale/ffa/views/SquareViews.kt similarity index 53% rename from app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt rename to app/src/main/java/audio/funkwhale/ffa/views/SquareViews.kt index e148e10..c8b3d33 100644 --- a/app/src/main/java/audio/funkwhale/ffa/views/SquareImageView.kt +++ b/app/src/main/java/audio/funkwhale/ffa/views/SquareViews.kt @@ -2,8 +2,25 @@ package audio.funkwhale.ffa.views import android.content.Context import android.util.AttributeSet +import android.view.View +import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageView +open class SquareView : View { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + val dimension = if(measuredWidth == 0 && measuredHeight > 0) measuredHeight else measuredWidth + + setMeasuredDimension(dimension, dimension) + } +} + + open class SquareImageView : AppCompatImageView { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 0729838..908cdfe 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -9,9 +9,8 @@ + app:layout_constraintBottom_toTopOf="@id/appbar_wrapper"> - - + android:background="@color/elevatedSurface" + app:target_header="@id/constraint_layout_placeholder"> - + + + + + + + + + + + + app:layoutDescription="@xml/fragment_now_playing_scene"> - + - + /> - - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4b32bc5..c8ef756 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,11 +11,11 @@ android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@id/appbar_wrapper"> + + android:id="@+id/nav_host_fragment_wrapper"> + android:background="@color/elevatedSurface" + app:target_header="@id/constraint_layout_placeholder"> + tools:layout="@layout/fragment_now_playing" + /> diff --git a/app/src/main/res/layout/fragment_now_playing.xml b/app/src/main/res/layout/fragment_now_playing.xml index 93434f6..efb3f53 100644 --- a/app/src/main/res/layout/fragment_now_playing.xml +++ b/app/src/main/res/layout/fragment_now_playing.xml @@ -1,25 +1,35 @@ - + + + + + + + + + + + + + - + app:layoutDescription="@xml/fragment_now_playing_scene"> + + layout="@layout/partial_now_playing_header" + /> - + app:tint="@color/controlForeground" + /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/partial_now_playing_controls.xml b/app/src/main/res/layout/partial_now_playing_controls.xml index 4196c71..6309610 100644 --- a/app/src/main/res/layout/partial_now_playing_controls.xml +++ b/app/src/main/res/layout/partial_now_playing_controls.xml @@ -16,9 +16,11 @@ + + android:layout_height="match_parent" + android:alpha="0"> - + + + + android:progressTint="@color/colorPrimaryDark" + /> + tools:src="@tools:sample/avatars" + /> + android:visibility="@{isBuffering ? View.VISIBLE : View.INVISIBLE, default=invisible}" + /> + tools:text="Supermassive Black Hole" + /> + tools:text="Muse" + /> + app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}" + /> + android:src="@drawable/next" + /> - + + \ No newline at end of file diff --git a/app/src/main/res/xml/fragment_now_playing_scene.xml b/app/src/main/res/xml/fragment_now_playing_scene.xml new file mode 100644 index 0000000..8044949 --- /dev/null +++ b/app/src/main/res/xml/fragment_now_playing_scene.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +