make dragging be smooth

This commit is contained in:
Christian Schabesberger 2024-09-03 13:34:55 +02:00
parent 8ed25f5039
commit c0a006f238
6 changed files with 129 additions and 7 deletions

View File

@ -65,4 +65,5 @@ interface VideoPlayerViewModel {
fun onStorePlaylist() fun onStorePlaylist()
fun movePlaylistItem(from: Int, to: Int) fun movePlaylistItem(from: Int, to: Int)
fun removePlaylistItem(index: Int) fun removePlaylistItem(index: Int)
fun onStreamItemDragFinished()
} }

View File

@ -46,6 +46,7 @@ import net.newpipe.newplayer.Chapter
import net.newpipe.newplayer.utils.VideoSize import net.newpipe.newplayer.utils.VideoSize
import net.newpipe.newplayer.NewPlayer import net.newpipe.newplayer.NewPlayer
import net.newpipe.newplayer.ui.ContentScale import net.newpipe.newplayer.ui.ContentScale
import java.util.LinkedList
val VIDEOPLAYER_UI_STATE = "video_player_ui_state" val VIDEOPLAYER_UI_STATE = "video_player_ui_state"
@ -62,6 +63,9 @@ class VideoPlayerViewModelImpl @Inject constructor(
private val mutableUiState = MutableStateFlow(VideoPlayerUIState.DEFAULT) private val mutableUiState = MutableStateFlow(VideoPlayerUIState.DEFAULT)
private var currentContentRatio = 1F private var currentContentRatio = 1F
private var playlistItemToBeMoved: Int? = null
private var playlistItemNewPosition: Int = 0
private var uiVisibilityJob: Job? = null private var uiVisibilityJob: Job? = null
private var progressUpdaterJob: Job? = null private var progressUpdaterJob: Job? = null
@ -422,13 +426,34 @@ class VideoPlayerViewModelImpl @Inject constructor(
} }
override fun movePlaylistItem(from: Int, to: Int) { override fun movePlaylistItem(from: Int, to: Int) {
newPlayer?.movePlaylistItem(from, to) if(playlistItemToBeMoved == null) {
playlistItemToBeMoved = from
}
playlistItemNewPosition = to
val tempList = LinkedList(uiState.value.playList)
val item = uiState.value.playList[from]
tempList.removeAt(from)
tempList.add(to, item)
mutableUiState.update {
it.copy(
playList = tempList
)
}
}
override fun onStreamItemDragFinished() {
playlistItemToBeMoved?.let {
newPlayer?.movePlaylistItem(it, playlistItemNewPosition)
}
playlistItemToBeMoved = null
} }
override fun removePlaylistItem(index: Int) { override fun removePlaylistItem(index: Int) {
newPlayer?.removePlaylistItem(index) newPlayer?.removePlaylistItem(index)
} }
private fun updateUiMode(newState: UIModeState) { private fun updateUiMode(newState: UIModeState) {
val newPlayMode = newState.toPlayMode() val newPlayMode = newState.toPlayMode()
val currentPlayMode = mutableUiState.value.uiMode.toPlayMode() val currentPlayMode = mutableUiState.value.uiMode.toPlayMode()

View File

@ -111,6 +111,10 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel {
println("dummy impl") println("dummy impl")
} }
override fun onStreamItemDragFinished() {
println("dummy impl")
}
override fun pause() { override fun pause() {
println("dummy pause") println("dummy pause")
} }

View File

@ -51,7 +51,9 @@ import net.newpipe.newplayer.ui.videoplayer.streamselect.ChapterItem
import net.newpipe.newplayer.ui.videoplayer.streamselect.ChapterSelectTopBar import net.newpipe.newplayer.ui.videoplayer.streamselect.ChapterSelectTopBar
import net.newpipe.newplayer.ui.videoplayer.streamselect.StreamItem import net.newpipe.newplayer.ui.videoplayer.streamselect.StreamItem
import net.newpipe.newplayer.ui.videoplayer.streamselect.StreamSelectTopBar import net.newpipe.newplayer.ui.videoplayer.streamselect.StreamSelectTopBar
import net.newpipe.newplayer.utils.ReorderHapticFeedbackType
import net.newpipe.newplayer.utils.getInsets import net.newpipe.newplayer.utils.getInsets
import net.newpipe.newplayer.utils.rememberReorderHapticFeedback
import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@ -122,15 +124,15 @@ fun ReorderableStreamItemsList(
viewModel: VideoPlayerViewModel, viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState uiState: VideoPlayerUIState
) { ) {
val haptic = rememberReorderHapticFeedback()
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val reorderableLazyListState = val reorderableLazyListState =
rememberReorderableLazyListState(lazyListState = lazyListState) { from, to -> rememberReorderableLazyListState(lazyListState = lazyListState) { from, to ->
haptic.performHapticFeedback(ReorderHapticFeedbackType.MOVE)
viewModel.movePlaylistItem(from.index, to.index) viewModel.movePlaylistItem(from.index, to.index)
} }
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
@ -149,7 +151,9 @@ fun ReorderableStreamItemsList(
thumbnail = playlistItem.thumbnail, thumbnail = playlistItem.thumbnail,
lengthInMs = playlistItem.lengthInS.toLong() * 1000, lengthInMs = playlistItem.lengthInS.toLong() * 1000,
onClicked = { viewModel.streamSelected(it) }, onClicked = { viewModel.streamSelected(it) },
reorderableScope = this@ReorderableItem reorderableScope = this@ReorderableItem,
haptic = haptic,
onDragFinished = viewModel::onStreamItemDragFinished
) )
} }
} }

View File

@ -22,6 +22,7 @@ package net.newpipe.newplayer.ui.videoplayer.streamselect
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -39,6 +40,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -55,6 +57,8 @@ import net.newpipe.newplayer.ui.CONTROLLER_UI_BACKGROUND_COLOR
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.utils.BitmapThumbnail import net.newpipe.newplayer.utils.BitmapThumbnail
import net.newpipe.newplayer.utils.OnlineThumbnail import net.newpipe.newplayer.utils.OnlineThumbnail
import net.newpipe.newplayer.utils.ReorderHapticFeedback
import net.newpipe.newplayer.utils.ReorderHapticFeedbackType
import net.newpipe.newplayer.utils.Thumbnail import net.newpipe.newplayer.utils.Thumbnail
import net.newpipe.newplayer.utils.VectorThumbnail import net.newpipe.newplayer.utils.VectorThumbnail
import net.newpipe.newplayer.utils.getLocale import net.newpipe.newplayer.utils.getLocale
@ -100,9 +104,14 @@ fun StreamItem(
thumbnail: Thumbnail?, thumbnail: Thumbnail?,
lengthInMs: Long, lengthInMs: Long,
onClicked: (Int) -> Unit, onClicked: (Int) -> Unit,
reorderableScope: ReorderableCollectionItemScope? onDragFinished: () -> Unit,
reorderableScope: ReorderableCollectionItemScope?,
haptic: ReorderHapticFeedback?
) { ) {
val locale = getLocale()!! val locale = getLocale()!!
val interactionSource = remember { MutableInteractionSource() }
Row( Row(
modifier = modifier modifier = modifier
.padding(5.dp) .padding(5.dp)
@ -159,7 +168,16 @@ fun StreamItem(
Modifier Modifier
.aspectRatio(1f) .aspectRatio(1f)
.fillMaxSize() .fillMaxSize()
.draggableHandle() .draggableHandle(
onDragStarted = {
haptic?.performHapticFeedback(ReorderHapticFeedbackType.START)
},
onDragStopped = {
haptic?.performHapticFeedback(ReorderHapticFeedbackType.END)
onDragFinished()
},
interactionSource = interactionSource,
)
} }
} else { } else {
Modifier Modifier
@ -190,7 +208,9 @@ fun StreamItemPreview() {
thumbnail = null, thumbnail = null,
lengthInMs = 15 * 60 * 1000, lengthInMs = 15 * 60 * 1000,
onClicked = {}, onClicked = {},
reorderableScope = null reorderableScope = null,
haptic = null,
onDragFinished = {}
) )
} }
} }

View File

@ -0,0 +1,68 @@
/*
* Copyright 2023 Calvin Liang
*
* @Author Calvin Liang
* @Author Christian Schabesberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.newpipe.newplayer.utils
import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
enum class ReorderHapticFeedbackType {
START,
MOVE,
END,
}
open class ReorderHapticFeedback {
open fun performHapticFeedback(type: ReorderHapticFeedbackType) {
// no-op
}
}
@Composable
fun rememberReorderHapticFeedback(): ReorderHapticFeedback {
val view = LocalView.current
val reorderHapticFeedback = remember {
object : ReorderHapticFeedback() {
override fun performHapticFeedback(type: ReorderHapticFeedbackType) {
when (type) {
ReorderHapticFeedbackType.START ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
view.performHapticFeedback(android.view.HapticFeedbackConstants.DRAG_START)
}
ReorderHapticFeedbackType.MOVE ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
view.performHapticFeedback(android.view.HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
}
ReorderHapticFeedbackType.END ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
view.performHapticFeedback(android.view.HapticFeedbackConstants.GESTURE_END)
}
}
}
}
}
return reorderHapticFeedback
}