brake videoplayer ui apart
This commit is contained in:
parent
e5294a198d
commit
fdd55bf4a1
|
@ -1,5 +1,24 @@
|
|||
package net.newpipe.newplayer.ui
|
||||
/* NewPlayer
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
*
|
||||
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||
*
|
||||
* NewPlayer 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.
|
||||
*
|
||||
* NewPlayer 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 NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.ui
|
||||
|
||||
enum class ContentScale {
|
||||
FILL,
|
||||
|
|
|
@ -20,14 +20,10 @@
|
|||
|
||||
package net.newpipe.newplayer.ui
|
||||
|
||||
import android.view.MotionEvent
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
|
@ -35,63 +31,26 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.systemGestures
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.foundation.layout.waterfall
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||
import androidx.compose.material.icons.automirrored.filled.VolumeUp
|
||||
import androidx.compose.material.icons.filled.FitScreen
|
||||
import androidx.compose.material.icons.filled.Fullscreen
|
||||
import androidx.compose.material.icons.filled.FullscreenExit
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material.icons.filled.SkipPrevious
|
||||
import androidx.compose.material.icons.filled.Subtitles
|
||||
import androidx.compose.material.icons.filled.Translate
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
import androidx.compose.ui.layout.onPlaced
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import net.newpipe.newplayer.R
|
||||
import net.newpipe.newplayer.ui.seeker.Seeker
|
||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||
import net.newpipe.newplayer.ui.theme.video_player_onSurface
|
||||
import net.newpipe.newplayer.ui.videoplayer.BottomUI
|
||||
import net.newpipe.newplayer.ui.videoplayer.CenterUI
|
||||
import net.newpipe.newplayer.ui.videoplayer.TopUI
|
||||
import net.newpipe.newplayer.ui.videoplayer.TouchUi
|
||||
|
||||
@Composable
|
||||
fun VideoPlayerControllerUI(
|
||||
|
@ -204,294 +163,6 @@ fun VideoPlayerControllerUI(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun TouchUi(
|
||||
modifier: Modifier,
|
||||
hideUi: () -> Unit,
|
||||
showUi: () -> Unit,
|
||||
uiVissible: Boolean,
|
||||
fullscreen: Boolean,
|
||||
) {
|
||||
Box(modifier = Modifier
|
||||
.pointerInteropFilter {
|
||||
when (it.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (uiVissible) {
|
||||
hideUi()
|
||||
} else {
|
||||
showUi()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}) {
|
||||
Surface(color = Color.Transparent, modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// TopUI
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Composable
|
||||
private fun TopUI(modifier: Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.Start, modifier = Modifier.weight(1F)) {
|
||||
Text("The Title", fontSize = 15.sp, fontWeight = FontWeight.Bold)
|
||||
Text(
|
||||
"The Channel",
|
||||
fontSize = 12.sp,
|
||||
|
||||
)
|
||||
}
|
||||
Button(
|
||||
onClick = { /*TODO*/ },
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent, contentColor = video_player_onSurface
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
"1080p", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ },
|
||||
) {
|
||||
Text(
|
||||
"1x", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.MenuBook,
|
||||
contentDescription = stringResource(R.string.widget_description_chapter_selection)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.List,
|
||||
contentDescription = stringResource(R.string.widget_descriptoin_playlist_item_selection)
|
||||
)
|
||||
}
|
||||
MainMenu()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun MainMenu() {
|
||||
var showMainMenu: Boolean by remember { mutableStateOf(false) }
|
||||
|
||||
var pixel_density = LocalDensity.current
|
||||
|
||||
var offsetY by remember {
|
||||
mutableStateOf(0.dp)
|
||||
}
|
||||
|
||||
Box {
|
||||
IconButton(onClick = { showMainMenu = true }, modifier = Modifier.onPlaced {
|
||||
offsetY = with(pixel_density) {
|
||||
it.size.height.toDp()
|
||||
}
|
||||
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(R.string.menu_item_more_settings)
|
||||
)
|
||||
}
|
||||
DropdownMenu(modifier = Modifier.align(Alignment.TopStart),
|
||||
offset = DpOffset(x = 0.dp, y = -offsetY),
|
||||
expanded = showMainMenu,
|
||||
onDismissRequest = { showMainMenu = false }) {
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_open_in_browser)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Language,
|
||||
contentDescription = stringResource(R.string.menu_item_open_in_browser)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_share_timestamp)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Share,
|
||||
contentDescription = stringResource(R.string.menu_item_share_timestamp)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.mute)) }, leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.VolumeUp,
|
||||
contentDescription = stringResource(R.string.mute)
|
||||
)
|
||||
}, onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_fit_screen)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.FitScreen,
|
||||
contentDescription = stringResource(R.string.menu_item_fit_screen)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_sub_titles)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Subtitles,
|
||||
contentDescription = stringResource(R.string.menu_item_sub_titles)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_language)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Translate,
|
||||
contentDescription = stringResource(R.string.menu_item_language)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// CenterUI
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Composable
|
||||
private fun CenterUI(
|
||||
modifier: Modifier,
|
||||
isPlaying: Boolean,
|
||||
isLoading: Boolean,
|
||||
play: () -> Unit,
|
||||
pause: () -> Unit,
|
||||
nextStream: () -> Unit,
|
||||
prevStream: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (!isLoading) {
|
||||
CenterControllButton(
|
||||
buttonModifier = Modifier.size(80.dp),
|
||||
iconModifier = Modifier.size(40.dp),
|
||||
icon = Icons.Filled.SkipPrevious,
|
||||
contentDescriptoion = stringResource(R.string.widget_description_previous_stream),
|
||||
onClick = prevStream
|
||||
)
|
||||
|
||||
CenterControllButton(
|
||||
buttonModifier = Modifier.size(80.dp),
|
||||
iconModifier = Modifier.size(60.dp),
|
||||
icon = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
||||
contentDescriptoion = stringResource(
|
||||
if (isPlaying) R.string.widget_description_pause
|
||||
else R.string.widget_description_play
|
||||
),
|
||||
onClick = if (isPlaying) pause else play
|
||||
)
|
||||
|
||||
CenterControllButton(
|
||||
buttonModifier = Modifier.size(80.dp),
|
||||
iconModifier = Modifier.size(40.dp),
|
||||
icon = Icons.Filled.SkipNext,
|
||||
contentDescriptoion = stringResource(R.string.widget_description_next_stream),
|
||||
onClick = nextStream
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CenterControllButton(
|
||||
buttonModifier: Modifier,
|
||||
iconModifier: Modifier,
|
||||
icon: ImageVector,
|
||||
contentDescriptoion: String?,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
),
|
||||
modifier = buttonModifier
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon, modifier = iconModifier, contentDescription = contentDescriptoion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// BottomUI
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Composable
|
||||
private fun BottomUI(
|
||||
modifier: Modifier,
|
||||
isFullscreen: Boolean,
|
||||
seekPosition: Float,
|
||||
switchToFullscreen: () -> Unit,
|
||||
switchToEmbeddedView: () -> Unit,
|
||||
seekPositionChanged: (Float) -> Unit,
|
||||
seekingFinished: () -> Unit
|
||||
) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = modifier
|
||||
) {
|
||||
Text("00:06:45")
|
||||
Seeker(
|
||||
Modifier.weight(1F),
|
||||
value = seekPosition,
|
||||
onValueChange = seekPositionChanged,
|
||||
onValueChangeFinished = seekingFinished
|
||||
)
|
||||
|
||||
//Slider(value = 0.4F, onValueChange = {}, modifier = Modifier.weight(1F))
|
||||
|
||||
Text("00:09:40")
|
||||
|
||||
IconButton(onClick = if (isFullscreen) switchToEmbeddedView else switchToFullscreen) {
|
||||
Icon(
|
||||
imageVector = if (isFullscreen) Icons.Filled.FullscreenExit
|
||||
else Icons.Filled.Fullscreen,
|
||||
contentDescription = stringResource(R.string.widget_description_toggle_fullscreen)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
@ -512,9 +183,13 @@ fun PreviewBackgroundSurface(
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// Preview
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||
@Composable
|
||||
fun VideoPlayerControllerUIPreviewEmbeded() {
|
||||
fun VideoPlayerControllerUIPreviewEmbedded() {
|
||||
VideoPlayerTheme {
|
||||
PreviewBackgroundSurface {
|
||||
VideoPlayerControllerUI(isPlaying = false,
|
||||
|
|
|
@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.aspectRatio
|
|||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/* NewPlayer
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
*
|
||||
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||
*
|
||||
* NewPlayer 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.
|
||||
*
|
||||
* NewPlayer 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 NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.ui.videoplayer
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Fullscreen
|
||||
import androidx.compose.material.icons.filled.FullscreenExit
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import net.newpipe.newplayer.R
|
||||
import net.newpipe.newplayer.ui.seeker.Seeker
|
||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||
|
||||
@Composable
|
||||
fun BottomUI(
|
||||
modifier: Modifier,
|
||||
isFullscreen: Boolean,
|
||||
seekPosition: Float,
|
||||
switchToFullscreen: () -> Unit,
|
||||
switchToEmbeddedView: () -> Unit,
|
||||
seekPositionChanged: (Float) -> Unit,
|
||||
seekingFinished: () -> Unit
|
||||
) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = modifier
|
||||
) {
|
||||
Text("00:06:45")
|
||||
Seeker(
|
||||
Modifier.weight(1F),
|
||||
value = seekPosition,
|
||||
onValueChange = seekPositionChanged,
|
||||
onValueChangeFinished = seekingFinished
|
||||
)
|
||||
|
||||
//Slider(value = 0.4F, onValueChange = {}, modifier = Modifier.weight(1F))
|
||||
|
||||
Text("00:09:40")
|
||||
|
||||
IconButton(onClick = if (isFullscreen) switchToEmbeddedView else switchToFullscreen) {
|
||||
Icon(
|
||||
imageVector = if (isFullscreen) Icons.Filled.FullscreenExit
|
||||
else Icons.Filled.Fullscreen,
|
||||
contentDescription = stringResource(R.string.widget_description_toggle_fullscreen)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// Preview
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||
@Composable
|
||||
fun VideoPlayerControllerBottomUIPreview() {
|
||||
VideoPlayerTheme {
|
||||
Surface(color = Color.Black) {
|
||||
BottomUI(
|
||||
modifier = Modifier,
|
||||
isFullscreen = true,
|
||||
seekPosition = 0.4F,
|
||||
switchToFullscreen = { },
|
||||
switchToEmbeddedView = { },
|
||||
seekPositionChanged = {}
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/* NewPlayer
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
*
|
||||
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||
*
|
||||
* NewPlayer 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.
|
||||
*
|
||||
* NewPlayer 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 NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.ui.videoplayer
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material.icons.filled.SkipPrevious
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.newpipe.newplayer.R
|
||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||
|
||||
@Composable
|
||||
fun CenterUI(
|
||||
modifier: Modifier,
|
||||
isPlaying: Boolean,
|
||||
isLoading: Boolean,
|
||||
play: () -> Unit,
|
||||
pause: () -> Unit,
|
||||
nextStream: () -> Unit,
|
||||
prevStream: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (!isLoading) {
|
||||
CenterControllButton(
|
||||
buttonModifier = Modifier.size(80.dp),
|
||||
iconModifier = Modifier.size(40.dp),
|
||||
icon = Icons.Filled.SkipPrevious,
|
||||
contentDescriptoion = stringResource(R.string.widget_description_previous_stream),
|
||||
onClick = prevStream
|
||||
)
|
||||
|
||||
CenterControllButton(
|
||||
buttonModifier = Modifier.size(80.dp),
|
||||
iconModifier = Modifier.size(60.dp),
|
||||
icon = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
||||
contentDescriptoion = stringResource(
|
||||
if (isPlaying) R.string.widget_description_pause
|
||||
else R.string.widget_description_play
|
||||
),
|
||||
onClick = if (isPlaying) pause else play
|
||||
)
|
||||
|
||||
CenterControllButton(
|
||||
buttonModifier = Modifier.size(80.dp),
|
||||
iconModifier = Modifier.size(40.dp),
|
||||
icon = Icons.Filled.SkipNext,
|
||||
contentDescriptoion = stringResource(R.string.widget_description_next_stream),
|
||||
onClick = nextStream
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CenterControllButton(
|
||||
buttonModifier: Modifier,
|
||||
iconModifier: Modifier,
|
||||
icon: ImageVector,
|
||||
contentDescriptoion: String?,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
),
|
||||
modifier = buttonModifier
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon, modifier = iconModifier, contentDescription = contentDescriptoion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// Preview
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||
@Composable
|
||||
fun VideoPlayerControllerUICenterUIPreview() {
|
||||
VideoPlayerTheme {
|
||||
Surface(color = Color.Black) {
|
||||
CenterUI(
|
||||
modifier = Modifier,
|
||||
isPlaying = true,
|
||||
isLoading = false,
|
||||
play = { },
|
||||
pause = { },
|
||||
nextStream = { }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/* NewPlayer
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
*
|
||||
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||
*
|
||||
* NewPlayer 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.
|
||||
*
|
||||
* NewPlayer 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 NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.ui.videoplayer
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.VolumeUp
|
||||
import androidx.compose.material.icons.filled.FitScreen
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material.icons.filled.Subtitles
|
||||
import androidx.compose.material.icons.filled.Translate
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onPlaced
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.newpipe.newplayer.R
|
||||
|
||||
@Composable
|
||||
fun DropDownMenu() {
|
||||
var showMainMenu: Boolean by remember { mutableStateOf(false) }
|
||||
|
||||
var pixel_density = LocalDensity.current
|
||||
|
||||
var offsetY by remember {
|
||||
mutableStateOf(0.dp)
|
||||
}
|
||||
|
||||
Box {
|
||||
IconButton(onClick = { showMainMenu = true }, modifier = Modifier.onPlaced {
|
||||
offsetY = with(pixel_density) {
|
||||
it.size.height.toDp()
|
||||
}
|
||||
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(R.string.menu_item_more_settings)
|
||||
)
|
||||
}
|
||||
DropdownMenu(modifier = Modifier.align(Alignment.TopStart),
|
||||
offset = DpOffset(x = 0.dp, y = -offsetY),
|
||||
expanded = showMainMenu,
|
||||
onDismissRequest = { showMainMenu = false }) {
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_open_in_browser)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Language,
|
||||
contentDescription = stringResource(R.string.menu_item_open_in_browser)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_share_timestamp)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Share,
|
||||
contentDescription = stringResource(R.string.menu_item_share_timestamp)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.mute)) }, leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.VolumeUp,
|
||||
contentDescription = stringResource(R.string.mute)
|
||||
)
|
||||
}, onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_fit_screen)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.FitScreen,
|
||||
contentDescription = stringResource(R.string.menu_item_fit_screen)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_sub_titles)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Subtitles,
|
||||
contentDescription = stringResource(R.string.menu_item_sub_titles)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.menu_item_language)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Translate,
|
||||
contentDescription = stringResource(R.string.menu_item_language)
|
||||
)
|
||||
},
|
||||
onClick = { /*TODO*/ showMainMenu = false })
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/* NewPlayer
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
*
|
||||
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||
*
|
||||
* NewPlayer 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.
|
||||
*
|
||||
* NewPlayer 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 NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.ui.videoplayer
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import net.newpipe.newplayer.R
|
||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||
import net.newpipe.newplayer.ui.theme.video_player_onSurface
|
||||
|
||||
@Composable
|
||||
fun TopUI(modifier: Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.Start, modifier = Modifier.weight(1F)) {
|
||||
Text("The Title", fontSize = 15.sp, fontWeight = FontWeight.Bold)
|
||||
Text(
|
||||
"The Channel",
|
||||
fontSize = 12.sp,
|
||||
|
||||
)
|
||||
}
|
||||
Button(
|
||||
onClick = { /*TODO*/ },
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent, contentColor = video_player_onSurface
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
"1080p", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ },
|
||||
) {
|
||||
Text(
|
||||
"1x", fontWeight = FontWeight.Bold, modifier = Modifier.padding(0.dp)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.MenuBook,
|
||||
contentDescription = stringResource(R.string.widget_description_chapter_selection)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { /*TODO*/ },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.List,
|
||||
contentDescription = stringResource(R.string.widget_descriptoin_playlist_item_selection)
|
||||
)
|
||||
}
|
||||
DropDownMenu()
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// Preview
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
|
||||
@Composable
|
||||
fun VideoPlayerControllerTopUIPreview() {
|
||||
VideoPlayerTheme {
|
||||
Surface(color = Color.Black) {
|
||||
TopUI(modifier = Modifier)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/* NewPlayer
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
*
|
||||
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||
*
|
||||
* NewPlayer 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.
|
||||
*
|
||||
* NewPlayer 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 NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.ui.videoplayer
|
||||
|
||||
import android.view.MotionEvent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun TouchUi(
|
||||
modifier: Modifier,
|
||||
hideUi: () -> Unit,
|
||||
showUi: () -> Unit,
|
||||
uiVissible: Boolean,
|
||||
fullscreen: Boolean,
|
||||
) {
|
||||
Box(modifier = Modifier
|
||||
.pointerInteropFilter {
|
||||
when (it.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (uiVissible) {
|
||||
hideUi()
|
||||
} else {
|
||||
showUi()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}) {
|
||||
Surface(color = Color.Transparent, modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue