brake videoplayer ui apart

This commit is contained in:
Christian Schabesberger 2024-07-29 14:31:03 +02:00
parent e5294a198d
commit fdd55bf4a1
8 changed files with 575 additions and 337 deletions

View File

@ -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 { enum class ContentScale {
FILL, FILL,

View File

@ -20,14 +20,10 @@
package net.newpipe.newplayer.ui package net.newpipe.newplayer.ui
import android.view.MotionEvent
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box 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.WindowInsets
import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.displayCutout 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.systemGestures import androidx.compose.foundation.layout.systemGestures
import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.waterfall import androidx.compose.foundation.layout.waterfall
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding 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.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.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp 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.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 @Composable
fun VideoPlayerControllerUI( 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 // Utils
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
@ -512,9 +183,13 @@ fun PreviewBackgroundSurface(
} }
} }
///////////////////////////////////////////////////////////////////
// Preview
///////////////////////////////////////////////////////////////////
@Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape") @Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape")
@Composable @Composable
fun VideoPlayerControllerUIPreviewEmbeded() { fun VideoPlayerControllerUIPreviewEmbedded() {
VideoPlayerTheme { VideoPlayerTheme {
PreviewBackgroundSurface { PreviewBackgroundSurface {
VideoPlayerControllerUI(isPlaying = false, VideoPlayerControllerUI(isPlaying = false,

View File

@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect

View File

@ -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 = {}
) {
}
}
}
}

View File

@ -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 = { }) {
}
}
}
}

View File

@ -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 })
}
}
}

View File

@ -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)
}
}
}

View File

@ -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()) {
}
}
}