kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale-android
Use MotionLayout to animate bottom sheet opening
rodzic
b924a0c655
commit
056e3a4d66
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
(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) {
|
||||
// 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)
|
||||
// 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)
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {
|
||||
// Animate the cover and other elements of the bottom sheet
|
||||
onBottomSheetDrag(slideOffset)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
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<Response.Queue>()?.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(), "")
|
||||
|
||||
|
@ -306,6 +315,7 @@ class MainActivity : AppCompatActivity() {
|
|||
lifecycleScope,
|
||||
command.tracks
|
||||
)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,9 +29,9 @@ 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) {
|
||||
private val binding by lazy { FragmentNowPlayingBinding.bind(requireView()) }
|
||||
|
@ -40,14 +39,7 @@ class NowPlayingFragment : Fragment(R.layout.fragment_now_playing) {
|
|||
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(
|
||||
|
|
|
@ -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<NowPlayingBottomSheet>()
|
||||
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<View>(targetHeaderId)?.apply {
|
||||
behavior.setPeekHeight(this.measuredHeight, false)
|
||||
behavior.setPeekHeight(this.height, false)
|
||||
this.setOnClickListener { this@NowPlayingBottomSheet.toggle() }
|
||||
} ?: hide()
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -9,9 +9,8 @@
|
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/surface"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_weight="10">
|
||||
app:layout_constraintBottom_toTopOf="@id/appbar_wrapper">
|
||||
<LinearLayout
|
||||
android:id="@+id/nav_host_fragment_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -38,21 +37,13 @@
|
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:layout="@layout/partial_queue" />
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
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">
|
||||
android:background="@color/elevatedSurface"
|
||||
app:target_header="@id/constraint_layout_placeholder">
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/now_playing"
|
||||
android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment"
|
||||
|
|
|
@ -3,56 +3,63 @@
|
|||
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
|
||||
|
||||
<data>
|
||||
<import type="androidx.lifecycle.LiveData" />
|
||||
<import type="android.view.View" />
|
||||
<import type="android.graphics.drawable.Drawable" />
|
||||
<variable name="isBuffering" type="LiveData<Boolean>" />
|
||||
<variable name="isPlaying" type="LiveData<Boolean>" />
|
||||
<variable name="progress" type="LiveData<Integer>" />
|
||||
<variable name="currentTrackTitle" type="LiveData<String>" />
|
||||
<variable name="currentTrackArtist" type="LiveData<String>" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.motion.widget.MotionLayout
|
||||
android:id="@+id/now_playing_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/elevatedSurface">
|
||||
app:layoutDescription="@xml/fragment_now_playing_scene">
|
||||
|
||||
<include
|
||||
android:id="@+id/header"
|
||||
layout="@layout/partial_now_playing_header" />
|
||||
<include android:id="@+id/header" layout="@layout/partial_now_playing_header" />
|
||||
|
||||
<audio.funkwhale.ffa.views.SquareImageView
|
||||
android:id="@+id/now_playing_detail_cover"
|
||||
<audio.funkwhale.ffa.views.SquareView
|
||||
android:id="@+id/detail_image_placeholder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintTop_toBottomOf="@id/header"
|
||||
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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:layout_constraintEnd_toEndOf="@id/detail_image_placeholder"
|
||||
app:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
|
||||
app:tint="@color/controlForeground"
|
||||
/>
|
||||
|
||||
|
||||
<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"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/now_playing_detail_cover"
|
||||
app:layout_constraintStart_toEndOf="@id/detail_image_placeholder"
|
||||
android:alpha="0"
|
||||
android:background="@color/elevatedSurface"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||
</layout>
|
|
@ -11,11 +11,11 @@
|
|||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
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"
|
||||
>
|
||||
android:id="@+id/nav_host_fragment_wrapper">
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
|
@ -30,18 +30,17 @@
|
|||
|
||||
<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">
|
||||
android:background="@color/elevatedSurface"
|
||||
app:target_header="@id/constraint_layout_placeholder">
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/now_playing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:name="audio.funkwhale.ffa.fragments.NowPlayingFragment"
|
||||
tools:layout="@layout/fragment_now_playing"/>
|
||||
tools:layout="@layout/fragment_now_playing"
|
||||
/>
|
||||
</audio.funkwhale.ffa.views.NowPlayingBottomSheet>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
<?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
|
||||
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<Boolean>" />
|
||||
<variable name="isPlaying" type="LiveData<Boolean>" />
|
||||
<variable name="progress" type="LiveData<Integer>" />
|
||||
<variable name="currentTrackTitle" type="LiveData<String>" />
|
||||
<variable name="currentTrackArtist" type="LiveData<String>" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.motion.widget.MotionLayout
|
||||
android:id="@+id/now_playing_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/elevatedSurface">
|
||||
app:layoutDescription="@xml/fragment_now_playing_scene">
|
||||
|
||||
<include
|
||||
android:id="@+id/header"
|
||||
layout="@layout/partial_now_playing_header" />
|
||||
layout="@layout/partial_now_playing_header"
|
||||
/>
|
||||
|
||||
<audio.funkwhale.ffa.views.SquareImageView
|
||||
android:id="@+id/now_playing_detail_cover"
|
||||
<audio.funkwhale.ffa.views.SquareView
|
||||
android:id="@+id/detail_image_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header"
|
||||
app:srcCompat="@drawable/cover"
|
||||
tools:src="@tools:sample/avatars"
|
||||
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
|
@ -27,23 +37,23 @@
|
|||
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"
|
||||
app:layout_constraintEnd_toEndOf="@id/detail_image_placeholder"
|
||||
app:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
|
||||
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" />
|
||||
app:tint="@color/controlForeground"
|
||||
/>
|
||||
|
||||
<include
|
||||
android:id="@+id/controls"
|
||||
layout="@layout/partial_now_playing_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/now_playing_detail_cover"
|
||||
app:layout_constraintTop_toBottomOf="@id/detail_image_placeholder"
|
||||
/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||
</layout>
|
|
@ -16,9 +16,11 @@
|
|||
<variable name="isPlaying" type="LiveData<Boolean>" />
|
||||
<variable name="progress" type="LiveData<Integer>" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0">
|
||||
<TextView
|
||||
android:id="@+id/current_playing_details_title"
|
||||
android:layout_width="0dp"
|
||||
|
@ -84,8 +86,8 @@
|
|||
android:id="@+id/now_playing_details_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:max="100"
|
||||
android:layout_margin="8dp"
|
||||
android:max="100"
|
||||
android:progress="@{progress, default=40}"
|
||||
android:progressBackgroundTint="#cacaca"
|
||||
android:progressTint="@color/controlForeground"
|
||||
|
|
|
@ -14,29 +14,34 @@
|
|||
<variable name="currentTrackArtist" type="LiveData<String>" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<merge>
|
||||
<!-- Placeholder for setting constraints and interacting -->
|
||||
<View
|
||||
android:id="@+id/constraint_layout_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/now_playing_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/constraint_layout_placeholder"
|
||||
android:progress="@{progress, default=40}"
|
||||
android:progressTint="@color/colorPrimaryDark" />
|
||||
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:layout_constraintStart_toStartOf="@id/constraint_layout_placeholder"
|
||||
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
|
||||
app:layout_constraintBottom_toBottomOf="@id/constraint_layout_placeholder"
|
||||
app:srcCompat="@drawable/cover"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
tools:src="@tools:sample/avatars"
|
||||
/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/now_playing_buffering"
|
||||
|
@ -46,9 +51,8 @@
|
|||
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.INVISIBLE, default=invisible}" />
|
||||
android:visibility="@{isBuffering ? View.VISIBLE : View.INVISIBLE, default=invisible}"
|
||||
/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header_controls"
|
||||
|
@ -56,9 +60,10 @@
|
|||
android:layout_height="0dp"
|
||||
app:layout_constraintHorizontal_weight="10"
|
||||
app:layout_constraintStart_toEndOf="@id/now_playing_cover"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/constraint_layout_placeholder"
|
||||
app:layout_constraintTop_toBottomOf="@id/now_playing_progress"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/constraint_layout_placeholder"
|
||||
android:background="@color/elevatedSurface"
|
||||
android:padding="4dp">
|
||||
|
||||
<TextView
|
||||
|
@ -71,7 +76,8 @@
|
|||
android:text="@{currentTrackTitle}"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
tools:text="Supermassive Black Hole" />
|
||||
tools:text="Supermassive Black Hole"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
|
@ -82,7 +88,8 @@
|
|||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:text="@{currentTrackArtist}"
|
||||
tools:text="Muse" />
|
||||
tools:text="Muse"
|
||||
/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/now_playing_toggle"
|
||||
|
@ -91,7 +98,8 @@
|
|||
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}" />
|
||||
app:icon="@{isPlaying ? @drawable/pause : @drawable/play, default=@drawable/play}"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/now_playing_next"
|
||||
|
@ -100,7 +108,9 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
style="@style/IconButton"
|
||||
android:contentDescription="@string/control_next"
|
||||
android:src="@drawable/next" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:src="@drawable/next"
|
||||
/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</merge>
|
||||
|
||||
</layout>
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MotionScene
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:motion="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ConstraintSet android:id="@+id/start">
|
||||
<Constraint android:id="@id/now_playing_details_info">
|
||||
<PropertySet android:alpha="0" android:visibility="invisible" />
|
||||
</Constraint>
|
||||
<Constraint android:id="@id/header_controls">
|
||||
<PropertySet android:alpha="1" android:visibility="visible" />
|
||||
</Constraint>
|
||||
<Constraint android:id="@id/constraint_layout_placeholder">
|
||||
<PropertySet android:visibility="visible" />
|
||||
</Constraint>
|
||||
</ConstraintSet>
|
||||
|
||||
<ConstraintSet android:id="@+id/end">
|
||||
<Constraint
|
||||
android:id="@id/now_playing_cover"
|
||||
motion:layout_constraintEnd_toEndOf="@id/detail_image_placeholder"
|
||||
motion:layout_constraintStart_toStartOf="@id/detail_image_placeholder"
|
||||
motion:layout_constraintTop_toBottomOf="@id/detail_image_placeholder"
|
||||
motion:layout_constraintTop_toTopOf="@id/detail_image_placeholder"
|
||||
motion:transitionEasing="accelerate"
|
||||
/>
|
||||
<Constraint android:id="@id/now_playing_progress">
|
||||
<PropertySet android:alpha="0" android:visibility="gone" />
|
||||
</Constraint>
|
||||
<Constraint android:id="@id/header_controls">
|
||||
<PropertySet android:alpha="0" android:visibility="invisible" />
|
||||
</Constraint>
|
||||
<Constraint android:id="@id/constraint_layout_placeholder">
|
||||
<PropertySet android:visibility="invisible" />
|
||||
</Constraint>
|
||||
<Constraint android:id="@id/now_playing_details_info">
|
||||
<PropertySet android:alpha="1" android:visibility="visible"/>
|
||||
</Constraint>
|
||||
<Constraint android:id="@id/controls">
|
||||
<PropertySet android:alpha="1" />
|
||||
</Constraint>
|
||||
</ConstraintSet>
|
||||
|
||||
<Transition
|
||||
motion:constraintSetEnd="@id/end"
|
||||
motion:constraintSetStart="@+id/start"
|
||||
>
|
||||
<KeyFrameSet>
|
||||
<KeyPosition
|
||||
motion:percentX="1"
|
||||
motion:framePosition="50"
|
||||
motion:motionTarget="@id/now_playing_cover"
|
||||
motion:curveFit="spline"
|
||||
/>
|
||||
|
||||
<KeyAttribute
|
||||
android:alpha="0"
|
||||
motion:framePosition="10"
|
||||
motion:motionTarget="@id/header_controls"
|
||||
/>
|
||||
|
||||
<KeyPosition
|
||||
motion:percentX="1"
|
||||
motion:framePosition="50"
|
||||
motion:motionTarget="@id/header_controls"
|
||||
motion:curveFit="spline"
|
||||
/>
|
||||
|
||||
<KeyAttribute
|
||||
android:alpha="0"
|
||||
motion:framePosition="10"
|
||||
motion:motionTarget="@id/now_playing_progress"
|
||||
/>
|
||||
|
||||
<KeyAttribute
|
||||
android:alpha="0"
|
||||
motion:framePosition="90"
|
||||
motion:motionTarget="@id/now_playing_details_info"
|
||||
/>
|
||||
|
||||
<KeyAttribute
|
||||
android:alpha="0"
|
||||
motion:framePosition="90"
|
||||
motion:motionTarget="@id/controls"
|
||||
/>
|
||||
</KeyFrameSet>
|
||||
</Transition>
|
||||
</MotionScene>
|
Ładowanie…
Reference in New Issue