make dragging be smooth
This commit is contained in:
parent
8ed25f5039
commit
c0a006f238
|
@ -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()
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue