make item deletion almost possible
This commit is contained in:
parent
430bed20b6
commit
3dc5ca3e10
|
@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import net.newpipe.newplayer.model.UIModeState
|
||||||
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
||||||
import net.newpipe.newplayer.playerInternals.fetchPlaylistItem
|
import net.newpipe.newplayer.playerInternals.fetchPlaylistItem
|
||||||
import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromExoplayer
|
import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromExoplayer
|
||||||
|
@ -47,6 +48,7 @@ import kotlin.Exception
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
enum class PlayMode {
|
enum class PlayMode {
|
||||||
|
IDLE,
|
||||||
EMBEDDED_VIDEO,
|
EMBEDDED_VIDEO,
|
||||||
FULLSCREEN_VIDEO,
|
FULLSCREEN_VIDEO,
|
||||||
PIP,
|
PIP,
|
||||||
|
@ -73,7 +75,7 @@ interface NewPlayer {
|
||||||
val sharingLinkWithOffsetPossible: Boolean
|
val sharingLinkWithOffsetPossible: Boolean
|
||||||
var currentPosition: Long
|
var currentPosition: Long
|
||||||
var fastSeekAmountSec: Int
|
var fastSeekAmountSec: Int
|
||||||
val playBackMode: MutableStateFlow<PlayMode?>
|
val playBackMode: MutableStateFlow<PlayMode>
|
||||||
var shuffle: Boolean
|
var shuffle: Boolean
|
||||||
var repeatMode: RepeatMode
|
var repeatMode: RepeatMode
|
||||||
|
|
||||||
|
@ -162,7 +164,7 @@ class NewPlayerImpl(
|
||||||
|
|
||||||
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
|
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
|
||||||
|
|
||||||
override var playBackMode = MutableStateFlow<PlayMode?>(null)
|
override var playBackMode = MutableStateFlow(PlayMode.IDLE)
|
||||||
|
|
||||||
override var shuffle: Boolean
|
override var shuffle: Boolean
|
||||||
get() = internalPlayer.shuffleModeEnabled
|
get() = internalPlayer.shuffleModeEnabled
|
||||||
|
@ -281,6 +283,11 @@ class NewPlayerImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePlaylistItems() {
|
private fun updatePlaylistItems() {
|
||||||
|
if (internalPlayer.mediaItemCount == 0) {
|
||||||
|
playBackMode.update {
|
||||||
|
PlayMode.IDLE
|
||||||
|
}
|
||||||
|
}
|
||||||
playerScope.launch {
|
playerScope.launch {
|
||||||
val playlist =
|
val playlist =
|
||||||
getPlaylistItemsFromExoplayer(internalPlayer, repository, uniqueIdToIdLookup)
|
getPlaylistItemsFromExoplayer(internalPlayer, repository, uniqueIdToIdLookup)
|
||||||
|
|
|
@ -57,7 +57,7 @@ enum class UIModeState {
|
||||||
|
|
||||||
val isStreamSelect: Boolean
|
val isStreamSelect: Boolean
|
||||||
get() =
|
get() =
|
||||||
when(this) {
|
when (this) {
|
||||||
EMBEDDED_VIDEO_STREAM_SELECT -> true
|
EMBEDDED_VIDEO_STREAM_SELECT -> true
|
||||||
FULLSCREEN_VIDEO_STREAM_SELECT -> true
|
FULLSCREEN_VIDEO_STREAM_SELECT -> true
|
||||||
else -> false
|
else -> false
|
||||||
|
@ -65,7 +65,7 @@ enum class UIModeState {
|
||||||
|
|
||||||
val isChapterSelect: Boolean
|
val isChapterSelect: Boolean
|
||||||
get() =
|
get() =
|
||||||
when(this) {
|
when (this) {
|
||||||
EMBEDDED_VIDEO_CHAPTER_SELECT -> true
|
EMBEDDED_VIDEO_CHAPTER_SELECT -> true
|
||||||
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
|
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
|
||||||
else -> false
|
else -> false
|
||||||
|
@ -73,14 +73,14 @@ enum class UIModeState {
|
||||||
|
|
||||||
val systemInsetsVisible: Boolean
|
val systemInsetsVisible: Boolean
|
||||||
get() =
|
get() =
|
||||||
when(this) {
|
when (this) {
|
||||||
FULLSCREEN_VIDEO -> false
|
FULLSCREEN_VIDEO -> false
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
val fitScreenRotation: Boolean
|
val fitScreenRotation: Boolean
|
||||||
get() =
|
get() =
|
||||||
when(this) {
|
when (this) {
|
||||||
FULLSCREEN_VIDEO -> true
|
FULLSCREEN_VIDEO -> true
|
||||||
FULLSCREEN_VIDEO_CONTROLLER_UI -> true
|
FULLSCREEN_VIDEO_CONTROLLER_UI -> true
|
||||||
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
|
FULLSCREEN_VIDEO_CHAPTER_SELECT -> true
|
||||||
|
@ -141,8 +141,8 @@ enum class UIModeState {
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toPlayMode() = when(this) {
|
fun toPlayMode() = when (this) {
|
||||||
PLACEHOLDER -> null
|
PLACEHOLDER -> PlayMode.IDLE
|
||||||
EMBEDDED_VIDEO -> PlayMode.EMBEDDED_VIDEO
|
EMBEDDED_VIDEO -> PlayMode.EMBEDDED_VIDEO
|
||||||
EMBEDDED_VIDEO_CONTROLLER_UI -> PlayMode.EMBEDDED_VIDEO
|
EMBEDDED_VIDEO_CONTROLLER_UI -> PlayMode.EMBEDDED_VIDEO
|
||||||
EMBEDDED_VIDEO_CHAPTER_SELECT -> PlayMode.EMBEDDED_VIDEO
|
EMBEDDED_VIDEO_CHAPTER_SELECT -> PlayMode.EMBEDDED_VIDEO
|
||||||
|
@ -153,7 +153,7 @@ enum class UIModeState {
|
||||||
FULLSCREEN_VIDEO_STREAM_SELECT -> PlayMode.FULLSCREEN_VIDEO
|
FULLSCREEN_VIDEO_STREAM_SELECT -> PlayMode.FULLSCREEN_VIDEO
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNextModeWhenBackPressed() = when(this) {
|
fun getNextModeWhenBackPressed() = when (this) {
|
||||||
EMBEDDED_VIDEO_CHAPTER_SELECT -> EMBEDDED_VIDEO
|
EMBEDDED_VIDEO_CHAPTER_SELECT -> EMBEDDED_VIDEO
|
||||||
EMBEDDED_VIDEO_STREAM_SELECT -> EMBEDDED_VIDEO
|
EMBEDDED_VIDEO_STREAM_SELECT -> EMBEDDED_VIDEO
|
||||||
FULLSCREEN_VIDEO -> EMBEDDED_VIDEO
|
FULLSCREEN_VIDEO -> EMBEDDED_VIDEO
|
||||||
|
@ -164,16 +164,14 @@ enum class UIModeState {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromPlayMode(playMode: PlayMode?) =
|
fun fromPlayMode(playMode: PlayMode) =
|
||||||
if (playMode != null)
|
when (playMode) {
|
||||||
when (playMode) {
|
PlayMode.IDLE -> PLACEHOLDER
|
||||||
PlayMode.EMBEDDED_VIDEO -> EMBEDDED_VIDEO
|
PlayMode.EMBEDDED_VIDEO -> EMBEDDED_VIDEO
|
||||||
PlayMode.FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO
|
PlayMode.FULLSCREEN_VIDEO -> FULLSCREEN_VIDEO
|
||||||
PlayMode.PIP -> TODO()
|
PlayMode.PIP -> TODO()
|
||||||
PlayMode.BACKGROUND -> TODO()
|
PlayMode.BACKGROUND -> TODO()
|
||||||
PlayMode.AUDIO_FOREGROUND -> TODO()
|
PlayMode.AUDIO_FOREGROUND -> TODO()
|
||||||
}
|
}
|
||||||
else PLACEHOLDER
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -145,6 +145,11 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
mutableUiState.update {
|
mutableUiState.update {
|
||||||
it.copy(playing = isPlaying, isLoading = false)
|
it.copy(playing = isPlaying, isLoading = false)
|
||||||
}
|
}
|
||||||
|
if(isPlaying && uiState.value.uiMode.controllerUiVisible) {
|
||||||
|
resetHideUiDelayedJob()
|
||||||
|
} else {
|
||||||
|
uiVisibilityJob?.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
|
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
|
||||||
|
@ -181,6 +186,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
newPlayer.playBackMode.collect { newMode ->
|
newPlayer.playBackMode.collect { newMode ->
|
||||||
val currentMode = mutableUiState.value.uiMode.toPlayMode()
|
val currentMode = mutableUiState.value.uiMode.toPlayMode()
|
||||||
|
println("gurken mode: $currentMode newMode: $newMode")
|
||||||
if (currentMode != newMode) {
|
if (currentMode != newMode) {
|
||||||
mutableUiState.update {
|
mutableUiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
|
@ -55,6 +55,7 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import net.newpipe.newplayer.model.EmbeddedUiConfig
|
import net.newpipe.newplayer.model.EmbeddedUiConfig
|
||||||
|
import net.newpipe.newplayer.model.UIModeState
|
||||||
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
||||||
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
|
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
|
||||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
|
@ -116,10 +117,10 @@ fun VideoPlayerUI(
|
||||||
LaunchedEffect(
|
LaunchedEffect(
|
||||||
key1 = uiState.uiMode.systemInsetsVisible,
|
key1 = uiState.uiMode.systemInsetsVisible,
|
||||||
) {
|
) {
|
||||||
if (uiState.uiMode.fullscreen && !uiState.uiMode.systemInsetsVisible) {
|
if (uiState.uiMode.systemInsetsVisible) {
|
||||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
|
||||||
} else {
|
|
||||||
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
} else {
|
||||||
|
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,37 +162,45 @@ fun VideoPlayerUI(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set UI
|
// Set UI
|
||||||
Surface(
|
if (uiState.uiMode == UIModeState.PLACEHOLDER) {
|
||||||
modifier = Modifier.then(
|
VideoPlayerLoadingPlaceholder(uiState.embeddedUiRatio)
|
||||||
if (uiState.uiMode.fullscreen) Modifier.fillMaxSize()
|
} else {
|
||||||
else Modifier
|
Surface(
|
||||||
.fillMaxWidth()
|
modifier = Modifier.then(
|
||||||
.aspectRatio(uiState.embeddedUiRatio)
|
if (uiState.uiMode.fullscreen) Modifier.fillMaxSize()
|
||||||
), color = Color.Black
|
else Modifier
|
||||||
) {
|
.fillMaxWidth()
|
||||||
Box(contentAlignment = Alignment.Center) {
|
.aspectRatio(uiState.embeddedUiRatio)
|
||||||
PlaySurface(
|
), color = Color.Black
|
||||||
player = viewModel.newPlayer?.internalPlayer,
|
) {
|
||||||
lifecycle = lifecycle,
|
Box(contentAlignment = Alignment.Center) {
|
||||||
fitMode = uiState.contentFitMode,
|
PlaySurface(
|
||||||
uiRatio = if (uiState.uiMode.fullscreen) screenRatio
|
player = viewModel.newPlayer?.internalPlayer,
|
||||||
else uiState.embeddedUiRatio,
|
lifecycle = lifecycle,
|
||||||
contentRatio = uiState.contentRatio
|
fitMode = uiState.contentFitMode,
|
||||||
|
uiRatio = if (uiState.uiMode.fullscreen) screenRatio
|
||||||
|
else uiState.embeddedUiRatio,
|
||||||
|
contentRatio = uiState.contentRatio
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the checks if VideoPlayerControllerUI should be visible or not are done by
|
||||||
|
// The VideoPlayerControllerUI composable itself. This is because Visibility of
|
||||||
|
// the controller is more complicated than just using a simple if statement.
|
||||||
|
VideoPlayerControllerUI(
|
||||||
|
viewModel, uiState = uiState
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// the checks if VideoPlayerControllerUI should be visible or not are done by
|
AnimatedVisibility(visible = uiState.uiMode.isStreamSelect) {
|
||||||
// The VideoPlayerControllerUI composable itself. This is because Visibility of
|
StreamSelectUI(
|
||||||
// the controller is more complicated than just using a simple if statement.
|
viewModel = viewModel,
|
||||||
VideoPlayerControllerUI(
|
uiState = uiState,
|
||||||
viewModel, uiState = uiState
|
isChapterSelect = false
|
||||||
)
|
)
|
||||||
|
}
|
||||||
AnimatedVisibility(visible = uiState.uiMode.isStreamSelect) {
|
AnimatedVisibility(visible = uiState.uiMode.isChapterSelect) {
|
||||||
StreamSelectUI(viewModel = viewModel, uiState = uiState, isChapterSelect = false)
|
StreamSelectUI(viewModel = viewModel, uiState = uiState, isChapterSelect = true)
|
||||||
}
|
}
|
||||||
AnimatedVisibility(visible = uiState.uiMode.isChapterSelect) {
|
|
||||||
StreamSelectUI(viewModel = viewModel, uiState = uiState, isChapterSelect = true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,10 @@ fun ReorderableStreamItemsList(
|
||||||
haptic = haptic,
|
haptic = haptic,
|
||||||
onDragFinished = viewModel::onStreamItemDragFinished,
|
onDragFinished = viewModel::onStreamItemDragFinished,
|
||||||
isDragging = isDragging,
|
isDragging = isDragging,
|
||||||
isCurrentlyPlaying = playlistItem.uniqueId == uiState.currentlyPlaying.uniqueId
|
isCurrentlyPlaying = playlistItem.uniqueId == uiState.currentlyPlaying.uniqueId,
|
||||||
|
onDelete = {
|
||||||
|
viewModel.removePlaylistItem(index)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
package net.newpipe.newplayer.ui.videoplayer.streamselect
|
package net.newpipe.newplayer.ui.videoplayer.streamselect
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
@ -42,12 +43,17 @@ import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.DragHandle
|
import androidx.compose.material.icons.filled.DragHandle
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.SwipeToDismissBox
|
||||||
|
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
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
|
||||||
|
@ -78,12 +84,14 @@ import net.newpipe.newplayer.utils.getLocale
|
||||||
import net.newpipe.newplayer.utils.getTimeStringFromMs
|
import net.newpipe.newplayer.utils.getTimeStringFromMs
|
||||||
import sh.calvin.reorderable.ReorderableCollectionItemScope
|
import sh.calvin.reorderable.ReorderableCollectionItemScope
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun StreamItem(
|
fun StreamItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
playlistItem: PlaylistItem,
|
playlistItem: PlaylistItem,
|
||||||
onClicked: (Long) -> Unit,
|
onClicked: (Long) -> Unit,
|
||||||
onDragFinished: () -> Unit,
|
onDragFinished: () -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
reorderableScope: ReorderableCollectionItemScope?,
|
reorderableScope: ReorderableCollectionItemScope?,
|
||||||
haptic: ReorderHapticFeedback?,
|
haptic: ReorderHapticFeedback?,
|
||||||
isDragging: Boolean,
|
isDragging: Boolean,
|
||||||
|
@ -92,128 +100,151 @@ fun StreamItem(
|
||||||
val locale = getLocale()!!
|
val locale = getLocale()!!
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
Box(modifier = modifier
|
|
||||||
.height(60.dp)
|
|
||||||
.clip(ITEM_CORNER_SHAPE)
|
|
||||||
.clickable {
|
|
||||||
onClicked(playlistItem.uniqueId)
|
|
||||||
}) {
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
val dismissState = rememberSwipeToDismissBoxState(confirmValueChange = {
|
||||||
visible = isDragging,
|
when (it) {
|
||||||
enter = fadeIn(animationSpec = tween(200)),
|
SwipeToDismissBoxValue.StartToEnd -> {
|
||||||
exit = fadeOut(animationSpec = tween(400))
|
onDelete()
|
||||||
) {
|
true
|
||||||
Surface(
|
}
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
color = MaterialTheme.colorScheme.background,
|
SwipeToDismissBoxValue.EndToStart -> {
|
||||||
shadowElevation = 8.dp,
|
onDelete()
|
||||||
shape = ITEM_CORNER_SHAPE
|
true
|
||||||
) {}
|
}
|
||||||
|
|
||||||
|
SwipeToDismissBoxValue.Settled -> true
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
AnimatedVisibility(
|
SwipeToDismissBox(
|
||||||
visible = isCurrentlyPlaying,
|
modifier = modifier.height(60.dp), state = dismissState,
|
||||||
enter = fadeIn(animationSpec = tween(200)),
|
backgroundContent = {
|
||||||
exit = fadeOut(animationSpec = tween(400))
|
},
|
||||||
) {
|
) {
|
||||||
Surface(
|
Box(modifier = Modifier
|
||||||
modifier = Modifier.fillMaxSize(),
|
.fillMaxSize()
|
||||||
color = Color.White.copy(alpha = 0.2f),
|
.clip(ITEM_CORNER_SHAPE)
|
||||||
) {}
|
.clickable {
|
||||||
}
|
onClicked(playlistItem.uniqueId)
|
||||||
|
}) {
|
||||||
|
|
||||||
Row(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
modifier = modifier
|
visible = isDragging,
|
||||||
.padding(0.dp)
|
enter = fadeIn(animationSpec = tween(200)),
|
||||||
) {
|
exit = fadeOut(animationSpec = tween(400))
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.aspectRatio(16f / 9f)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
) {
|
||||||
val contentDescription = stringResource(R.string.stream_item_thumbnail)
|
|
||||||
Thumbnail(
|
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
thumbnail = playlistItem.thumbnail,
|
|
||||||
contentDescription = contentDescription,
|
|
||||||
shape = ITEM_CORNER_SHAPE
|
|
||||||
)
|
|
||||||
Surface(
|
Surface(
|
||||||
color = CONTROLLER_UI_BACKGROUND_COLOR,
|
modifier = Modifier.fillMaxSize(),
|
||||||
shape = ITEM_CORNER_SHAPE,
|
color = MaterialTheme.colorScheme.background,
|
||||||
|
shadowElevation = 8.dp,
|
||||||
|
shape = ITEM_CORNER_SHAPE
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
|
visible = isCurrentlyPlaying,
|
||||||
|
enter = fadeIn(animationSpec = tween(200)),
|
||||||
|
exit = fadeOut(animationSpec = tween(400))
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = Color.White.copy(alpha = 0.2f),
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(0.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.wrapContentSize()
|
.aspectRatio(16f / 9f)
|
||||||
.align(Alignment.BottomEnd)
|
.fillMaxSize()
|
||||||
.padding(4.dp)
|
) {
|
||||||
|
val contentDescription = stringResource(R.string.stream_item_thumbnail)
|
||||||
|
Thumbnail(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
thumbnail = playlistItem.thumbnail,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
shape = ITEM_CORNER_SHAPE
|
||||||
|
)
|
||||||
|
Surface(
|
||||||
|
color = CONTROLLER_UI_BACKGROUND_COLOR,
|
||||||
|
shape = ITEM_CORNER_SHAPE,
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentSize()
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = 4.dp,
|
||||||
|
end = 4.dp,
|
||||||
|
top = 0.5.dp,
|
||||||
|
bottom = 0.5.dp
|
||||||
|
),
|
||||||
|
text = getTimeStringFromMs(
|
||||||
|
playlistItem.lengthInS * 1000L,
|
||||||
|
locale,
|
||||||
|
leadingZerosForMinutes = false
|
||||||
|
),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(6.dp)
|
||||||
|
.weight(1f)
|
||||||
|
.wrapContentHeight()
|
||||||
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(
|
text = playlistItem.title,
|
||||||
start = 4.dp,
|
|
||||||
end = 4.dp,
|
|
||||||
top = 0.5.dp,
|
|
||||||
bottom = 0.5.dp
|
|
||||||
),
|
|
||||||
text = getTimeStringFromMs(
|
|
||||||
playlistItem.lengthInS * 1000L,
|
|
||||||
locale,
|
|
||||||
leadingZerosForMinutes = false
|
|
||||||
),
|
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = playlistItem.creator,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
IconButton(
|
||||||
|
modifier = if (reorderableScope != null) {
|
||||||
Column(
|
with(reorderableScope) {
|
||||||
modifier = Modifier
|
Modifier
|
||||||
.padding(6.dp)
|
.aspectRatio(1f)
|
||||||
.weight(1f)
|
.fillMaxSize()
|
||||||
.wrapContentHeight()
|
.draggableHandle(
|
||||||
.fillMaxWidth()
|
onDragStarted = {
|
||||||
) {
|
haptic?.performHapticFeedback(ReorderHapticFeedbackType.START)
|
||||||
Text(
|
},
|
||||||
text = playlistItem.title,
|
onDragStopped = {
|
||||||
fontSize = 14.sp,
|
haptic?.performHapticFeedback(ReorderHapticFeedbackType.END)
|
||||||
fontWeight = FontWeight.Bold,
|
onDragFinished()
|
||||||
maxLines = 1,
|
},
|
||||||
overflow = TextOverflow.Ellipsis
|
interactionSource = interactionSource,
|
||||||
)
|
)
|
||||||
Text(
|
}
|
||||||
text = playlistItem.creator,
|
} else {
|
||||||
fontSize = 13.sp,
|
|
||||||
fontWeight = FontWeight.Light,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(
|
|
||||||
modifier = if (reorderableScope != null) {
|
|
||||||
with(reorderableScope) {
|
|
||||||
Modifier
|
Modifier
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.draggableHandle(
|
},
|
||||||
onDragStarted = {
|
onClick = {}
|
||||||
haptic?.performHapticFeedback(ReorderHapticFeedbackType.START)
|
) {
|
||||||
},
|
Icon(
|
||||||
onDragStopped = {
|
imageVector = Icons.Filled.DragHandle,
|
||||||
haptic?.performHapticFeedback(ReorderHapticFeedbackType.END)
|
contentDescription = stringResource(R.string.stream_item_drag_handle)
|
||||||
onDragFinished()
|
)
|
||||||
},
|
}
|
||||||
interactionSource = interactionSource,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
.aspectRatio(1f)
|
|
||||||
.fillMaxSize()
|
|
||||||
},
|
|
||||||
onClick = {}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.DragHandle,
|
|
||||||
contentDescription = stringResource(R.string.stream_item_drag_handle)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +264,8 @@ fun StreamItemPreview() {
|
||||||
haptic = null,
|
haptic = null,
|
||||||
onDragFinished = {},
|
onDragFinished = {},
|
||||||
isDragging = false,
|
isDragging = false,
|
||||||
isCurrentlyPlaying = true
|
isCurrentlyPlaying = true,
|
||||||
|
onDelete = {println("has ben deleted")}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue