Switch tracks by swipe on cover
This commit is contained in:
parent
fcbce0fb98
commit
56a4102023
6 changed files with 123 additions and 6 deletions
|
@ -73,7 +73,7 @@ import org.oxycblt.auxio.util.getInteger
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class CoverView
|
open class CoverView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
FrameLayout(context, attrs, defStyleAttr) {
|
FrameLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.resolveNames
|
import org.oxycblt.auxio.music.resolveNames
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.playback.ui.StyledSeekBar
|
import org.oxycblt.auxio.playback.ui.StyledSeekBar
|
||||||
|
import org.oxycblt.auxio.playback.ui.SwipeCoverView
|
||||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
@ -58,7 +59,8 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
class PlaybackPanelFragment :
|
class PlaybackPanelFragment :
|
||||||
ViewBindingFragment<FragmentPlaybackPanelBinding>(),
|
ViewBindingFragment<FragmentPlaybackPanelBinding>(),
|
||||||
Toolbar.OnMenuItemClickListener,
|
Toolbar.OnMenuItemClickListener,
|
||||||
StyledSeekBar.Listener {
|
StyledSeekBar.Listener,
|
||||||
|
SwipeCoverView.OnSwipeListener {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
private val musicModel: MusicViewModel by activityViewModels()
|
private val musicModel: MusicViewModel by activityViewModels()
|
||||||
private val detailModel: DetailViewModel by activityViewModels()
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
|
@ -111,7 +113,7 @@ class PlaybackPanelFragment :
|
||||||
isSelected = true
|
isSelected = true
|
||||||
setOnClickListener { navigateToCurrentAlbum() }
|
setOnClickListener { navigateToCurrentAlbum() }
|
||||||
}
|
}
|
||||||
|
binding.playbackCover.onSwipeListener = this
|
||||||
binding.playbackSeekBar.listener = this
|
binding.playbackSeekBar.listener = this
|
||||||
|
|
||||||
// Set up actions
|
// Set up actions
|
||||||
|
@ -191,6 +193,14 @@ class PlaybackPanelFragment :
|
||||||
playbackModel.seekTo(positionDs)
|
playbackModel.seekTo(positionDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSwipePrevious() {
|
||||||
|
playbackModel.prev()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwipeNext() {
|
||||||
|
playbackModel.next()
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateSong(song: Song?) {
|
private fun updateSong(song: Song?) {
|
||||||
if (song == null) {
|
if (song == null) {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Auxio Project
|
||||||
|
* SwipeCoverView.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.auxio.playback.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.GestureDetector.OnGestureListener
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
|
import kotlin.math.abs
|
||||||
|
import org.oxycblt.auxio.image.CoverView
|
||||||
|
import org.oxycblt.auxio.util.isRtl
|
||||||
|
|
||||||
|
class SwipeCoverView
|
||||||
|
@JvmOverloads
|
||||||
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
|
CoverView(context, attrs, defStyleAttr), OnGestureListener {
|
||||||
|
|
||||||
|
private val gestureDetector = GestureDetector(context, this)
|
||||||
|
private val viewConfig = ViewConfiguration.get(context)
|
||||||
|
|
||||||
|
var onSwipeListener: OnSwipeListener? = null
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
return gestureDetector.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
|
||||||
|
return gestureDetector.onGenericMotionEvent(event) || super.onGenericMotionEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDown(e: MotionEvent): Boolean = true
|
||||||
|
|
||||||
|
override fun onShowPress(e: MotionEvent) = Unit
|
||||||
|
|
||||||
|
override fun onSingleTapUp(e: MotionEvent): Boolean = false
|
||||||
|
|
||||||
|
override fun onScroll(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean = false
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
e1 ?: return false
|
||||||
|
val diffY = e2.y - e1.y
|
||||||
|
val diffX = e2.x - e1.x
|
||||||
|
if (abs(diffX) > abs(diffY) &&
|
||||||
|
abs(diffX) > viewConfig.scaledTouchSlop &&
|
||||||
|
abs(velocityX) > viewConfig.scaledMinimumFlingVelocity
|
||||||
|
) {
|
||||||
|
if (diffX > 0) {
|
||||||
|
onSwipeRight()
|
||||||
|
} else {
|
||||||
|
onSwipeLeft()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongPress(e: MotionEvent) = Unit
|
||||||
|
|
||||||
|
private fun onSwipeRight() {
|
||||||
|
onSwipeListener?.run { if (isRtl) onSwipeNext() else onSwipePrevious() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSwipeLeft() {
|
||||||
|
onSwipeListener?.run { if (isRtl) onSwipePrevious() else onSwipeNext() }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnSwipeListener {
|
||||||
|
|
||||||
|
fun onSwipePrevious()
|
||||||
|
fun onSwipeNext()
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
app:title="@string/lbl_playback"
|
app:title="@string/lbl_playback"
|
||||||
tools:subtitle="@string/lbl_all_songs" />
|
tools:subtitle="@string/lbl_all_songs" />
|
||||||
|
|
||||||
<org.oxycblt.auxio.image.CoverView
|
<org.oxycblt.auxio.playback.ui.SwipeCoverView
|
||||||
android:id="@+id/playback_cover"
|
android:id="@+id/playback_cover"
|
||||||
style="@style/Widget.Auxio.Image.Full"
|
style="@style/Widget.Auxio.Image.Full"
|
||||||
android:layout_margin="@dimen/spacing_medium"
|
android:layout_margin="@dimen/spacing_medium"
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
app:title="@string/lbl_playback"
|
app:title="@string/lbl_playback"
|
||||||
tools:subtitle="@string/lbl_all_songs" />
|
tools:subtitle="@string/lbl_all_songs" />
|
||||||
|
|
||||||
<org.oxycblt.auxio.image.CoverView
|
<org.oxycblt.auxio.playback.ui.SwipeCoverView
|
||||||
android:id="@+id/playback_cover"
|
android:id="@+id/playback_cover"
|
||||||
style="@style/Widget.Auxio.Image.Full"
|
style="@style/Widget.Auxio.Image.Full"
|
||||||
android:layout_margin="@dimen/spacing_medium"
|
android:layout_margin="@dimen/spacing_medium"
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
app:title="@string/lbl_playback"
|
app:title="@string/lbl_playback"
|
||||||
tools:subtitle="@string/lbl_all_songs" />
|
tools:subtitle="@string/lbl_all_songs" />
|
||||||
|
|
||||||
<org.oxycblt.auxio.image.CoverView
|
<org.oxycblt.auxio.playback.ui.SwipeCoverView
|
||||||
android:id="@+id/playback_cover"
|
android:id="@+id/playback_cover"
|
||||||
style="@style/Widget.Auxio.Image.Full"
|
style="@style/Widget.Auxio.Image.Full"
|
||||||
android:layout_marginStart="@dimen/spacing_medium"
|
android:layout_marginStart="@dimen/spacing_medium"
|
||||||
|
|
Loading…
Reference in a new issue