get basic ui functions interact with player
This commit is contained in:
parent
1c01609af9
commit
e1d8447304
|
@ -83,6 +83,7 @@ dependencies {
|
||||||
implementation(libs.hilt.android)
|
implementation(libs.hilt.android)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation(libs.androidx.foundation)
|
implementation(libs.androidx.foundation)
|
||||||
|
implementation(libs.androidx.runtime.livedata)
|
||||||
ksp(libs.hilt.android.compiler)
|
ksp(libs.hilt.android.compiler)
|
||||||
ksp(libs.androidx.hilt.compiler)
|
ksp(libs.androidx.hilt.compiler)
|
||||||
implementation(libs.androidx.hilt.navigation.compose)
|
implementation(libs.androidx.hilt.navigation.compose)
|
||||||
|
|
|
@ -1,17 +1,61 @@
|
||||||
|
/* 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.model
|
package net.newpipe.newplayer.model
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import net.newpipe.newplayer.R
|
import net.newpipe.newplayer.R
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
data class VideoPlayerUIState(
|
||||||
|
var fullscreen: Boolean,
|
||||||
|
var playing: Boolean,
|
||||||
|
var uiVissible: Boolean
|
||||||
|
){
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = VideoPlayerUIState(
|
||||||
|
fullscreen = false,
|
||||||
|
playing = false,
|
||||||
|
uiVissible = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface VideoPlayerViewModel {
|
interface VideoPlayerViewModel {
|
||||||
val player: Player?
|
val player: Player?
|
||||||
|
val uiState: StateFlow<VideoPlayerUIState>
|
||||||
|
fun play()
|
||||||
|
fun pause()
|
||||||
|
fun prevStream()
|
||||||
|
fun nextStream()
|
||||||
|
fun switchToFullscreen()
|
||||||
|
fun switchToEmbeddedView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
@ -23,11 +67,51 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
|
|
||||||
val app = getApplication<Application>()
|
val app = getApplication<Application>()
|
||||||
|
|
||||||
|
private val mutableUiState = MutableStateFlow(
|
||||||
|
VideoPlayerUIState.DEFAULT
|
||||||
|
)
|
||||||
|
|
||||||
|
override val uiState = mutableUiState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
player.prepare()
|
player.prepare()
|
||||||
player.setMediaItem(MediaItem.fromUri(app.getString(R.string.ccc_6502_video)))
|
player.setMediaItem(MediaItem.fromUri(app.getString(R.string.ccc_6502_video)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun play() {
|
||||||
|
player.play()
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(playing = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pause() {
|
||||||
|
player.pause()
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(playing = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prevStream() {
|
||||||
|
println("imeplement prev stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nextStream() {
|
||||||
|
println("implement next stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun switchToEmbeddedView() {
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(fullscreen = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun switchToFullscreen() {
|
||||||
|
mutableUiState.update {
|
||||||
|
it.copy(fullscreen = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
player.release()
|
player.release()
|
||||||
}
|
}
|
||||||
|
@ -35,6 +119,30 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
companion object {
|
companion object {
|
||||||
val dummy = object : VideoPlayerViewModel {
|
val dummy = object : VideoPlayerViewModel {
|
||||||
override val player = null
|
override val player = null
|
||||||
|
override val uiState = MutableStateFlow(VideoPlayerUIState.DEFAULT)
|
||||||
|
override fun play() {
|
||||||
|
println("dummy impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun switchToEmbeddedView() {
|
||||||
|
println("dummy impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun switchToFullscreen() {
|
||||||
|
println("dummy impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pause() {
|
||||||
|
println("dummy pause")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prevStream() {
|
||||||
|
println("dummy impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nextStream() {
|
||||||
|
println("dummy impl")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package net.newpipe.newplayer.ui
|
package net.newpipe.newplayer.ui
|
||||||
|
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
|
@ -38,8 +39,10 @@ import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||||
import androidx.compose.material.icons.automirrored.filled.VolumeUp
|
import androidx.compose.material.icons.automirrored.filled.VolumeUp
|
||||||
import androidx.compose.material.icons.filled.FitScreen
|
import androidx.compose.material.icons.filled.FitScreen
|
||||||
import androidx.compose.material.icons.filled.Fullscreen
|
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.Language
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
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.PlayArrow
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material.icons.filled.SkipNext
|
import androidx.compose.material.icons.filled.SkipNext
|
||||||
|
@ -59,7 +62,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -78,7 +80,16 @@ import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
import net.newpipe.newplayer.ui.theme.video_player_onSurface
|
import net.newpipe.newplayer.ui.theme.video_player_onSurface
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoPlayerControllerUI() {
|
fun VideoPlayerControllerUI(
|
||||||
|
isPlaying: Boolean,
|
||||||
|
isFullscreen: Boolean,
|
||||||
|
play: () -> Unit,
|
||||||
|
pause: () -> Unit,
|
||||||
|
prevStream: () -> Unit,
|
||||||
|
nextStream: () -> Unit,
|
||||||
|
switchToFullscreen: () -> Unit,
|
||||||
|
switchToEmbeddedView: () -> Unit
|
||||||
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(), color = Color.Transparent
|
modifier = Modifier.fillMaxSize(), color = Color.Transparent
|
||||||
) {
|
) {
|
||||||
|
@ -90,13 +101,23 @@ fun VideoPlayerControllerUI() {
|
||||||
.defaultMinSize(minHeight = 45.dp)
|
.defaultMinSize(minHeight = 45.dp)
|
||||||
.padding(top = 4.dp, start = 16.dp, end = 16.dp)
|
.padding(top = 4.dp, start = 16.dp, end = 16.dp)
|
||||||
)
|
)
|
||||||
CenterUI(modifier = Modifier.align(Alignment.Center))
|
CenterUI(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
isPlaying,
|
||||||
|
play = play,
|
||||||
|
pause = pause,
|
||||||
|
prevStream = prevStream,
|
||||||
|
nextStream = nextStream
|
||||||
|
)
|
||||||
BottomUI(
|
BottomUI(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomStart)
|
.align(Alignment.BottomStart)
|
||||||
.padding(start = 16.dp, end = 16.dp)
|
.padding(start = 16.dp, end = 16.dp)
|
||||||
.defaultMinSize(minHeight = 40.dp)
|
.defaultMinSize(minHeight = 40.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
|
isFullscreen = isFullscreen,
|
||||||
|
switchToFullscreen,
|
||||||
|
switchToEmbeddedView
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,28 +262,45 @@ private fun MainMenu() {
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CenterUI(modifier: Modifier) {
|
private fun CenterUI(
|
||||||
|
modifier: Modifier,
|
||||||
|
isPlaying: Boolean,
|
||||||
|
play: () -> Unit,
|
||||||
|
pause: () -> Unit,
|
||||||
|
nextStream: () -> Unit,
|
||||||
|
prevStream: () -> Unit
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
CenterControllButton(buttonModifier = Modifier.size(80.dp),
|
CenterControllButton(
|
||||||
|
buttonModifier = Modifier.size(80.dp),
|
||||||
iconModifier = Modifier.size(40.dp),
|
iconModifier = Modifier.size(40.dp),
|
||||||
icon = Icons.Filled.SkipPrevious,
|
icon = Icons.Filled.SkipPrevious,
|
||||||
contentDescriptoion = stringResource(R.string.widget_description_previous_stream),
|
contentDescriptoion = stringResource(R.string.widget_description_previous_stream),
|
||||||
onClick = {})
|
onClick = prevStream
|
||||||
|
)
|
||||||
|
|
||||||
CenterControllButton(buttonModifier = Modifier.size(80.dp),
|
CenterControllButton(
|
||||||
|
buttonModifier = Modifier.size(80.dp),
|
||||||
iconModifier = Modifier.size(60.dp),
|
iconModifier = Modifier.size(60.dp),
|
||||||
icon = Icons.Filled.PlayArrow,
|
icon = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
||||||
contentDescriptoion = stringResource(R.string.widget_description_play),
|
contentDescriptoion = stringResource(
|
||||||
onClick = {})
|
if (isPlaying) R.string.widget_description_pause
|
||||||
CenterControllButton(buttonModifier = Modifier.size(80.dp),
|
else R.string.widget_description_play
|
||||||
|
),
|
||||||
|
onClick = if (isPlaying) pause else play
|
||||||
|
)
|
||||||
|
|
||||||
|
CenterControllButton(
|
||||||
|
buttonModifier = Modifier.size(80.dp),
|
||||||
iconModifier = Modifier.size(40.dp),
|
iconModifier = Modifier.size(40.dp),
|
||||||
icon = Icons.Filled.SkipNext,
|
icon = Icons.Filled.SkipNext,
|
||||||
contentDescriptoion = stringResource(R.string.widget_description_next_stream),
|
contentDescriptoion = stringResource(R.string.widget_description_next_stream),
|
||||||
onClick = {})
|
onClick = nextStream
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,8 +331,12 @@ private fun CenterControllButton(
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomUI(modifier: Modifier) {
|
private fun BottomUI(
|
||||||
var isFullscreen: Boolean by rememberSaveable { mutableStateOf(false) }
|
modifier: Modifier,
|
||||||
|
isFullscreen: Boolean,
|
||||||
|
switchToFullscreen: () -> Unit,
|
||||||
|
switchToEmbeddedView: () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||||
|
@ -308,10 +350,11 @@ private fun BottomUI(modifier: Modifier) {
|
||||||
Text("00:06:45")
|
Text("00:06:45")
|
||||||
Slider(value = 0.4F, onValueChange = {}, modifier = Modifier.weight(1F))
|
Slider(value = 0.4F, onValueChange = {}, modifier = Modifier.weight(1F))
|
||||||
Text("00:09:40")
|
Text("00:09:40")
|
||||||
IconButton(onClick = { isFullscreen = !isFullscreen }) {
|
IconButton(onClick = if(isFullscreen) switchToEmbeddedView else switchToFullscreen) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Fullscreen,
|
imageVector = if (isFullscreen) Icons.Filled.FullscreenExit
|
||||||
contentDescription = stringResource(R.string.widget_description_fullscreen)
|
else Icons.Filled.Fullscreen,
|
||||||
|
contentDescription = stringResource(R.string.widget_description_toggle_fullscreen)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,7 +385,14 @@ fun PreviewBackgroundSurface(
|
||||||
fun VideoPlayerControllerUIPreviewEmbeded() {
|
fun VideoPlayerControllerUIPreviewEmbeded() {
|
||||||
VideoPlayerTheme {
|
VideoPlayerTheme {
|
||||||
PreviewBackgroundSurface {
|
PreviewBackgroundSurface {
|
||||||
VideoPlayerControllerUI()
|
VideoPlayerControllerUI(isPlaying = false,
|
||||||
|
isFullscreen = false,
|
||||||
|
play = {},
|
||||||
|
pause = {},
|
||||||
|
prevStream = {},
|
||||||
|
nextStream = {},
|
||||||
|
switchToFullscreen = {},
|
||||||
|
switchToEmbeddedView = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,7 +402,14 @@ fun VideoPlayerControllerUIPreviewEmbeded() {
|
||||||
fun VideoPlayerControllerUIPreviewLandscape() {
|
fun VideoPlayerControllerUIPreviewLandscape() {
|
||||||
VideoPlayerTheme {
|
VideoPlayerTheme {
|
||||||
PreviewBackgroundSurface {
|
PreviewBackgroundSurface {
|
||||||
VideoPlayerControllerUI()
|
VideoPlayerControllerUI(isPlaying = true,
|
||||||
|
isFullscreen = true,
|
||||||
|
play = {},
|
||||||
|
pause = {},
|
||||||
|
prevStream = {},
|
||||||
|
nextStream = {},
|
||||||
|
switchToEmbeddedView = {},
|
||||||
|
switchToFullscreen = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,7 +419,15 @@ fun VideoPlayerControllerUIPreviewLandscape() {
|
||||||
fun VideoPlayerControllerUIPreviewPortrait() {
|
fun VideoPlayerControllerUIPreviewPortrait() {
|
||||||
VideoPlayerTheme {
|
VideoPlayerTheme {
|
||||||
PreviewBackgroundSurface {
|
PreviewBackgroundSurface {
|
||||||
VideoPlayerControllerUI()
|
VideoPlayerControllerUI(
|
||||||
|
isPlaying = false,
|
||||||
|
isFullscreen = true,
|
||||||
|
play = {},
|
||||||
|
pause = {},
|
||||||
|
prevStream = {},
|
||||||
|
nextStream = {},
|
||||||
|
switchToEmbeddedView = {},
|
||||||
|
switchToFullscreen = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
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
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
@ -47,9 +48,12 @@ fun VideoPlayerUI(
|
||||||
viewModel: VideoPlayerViewModel = hiltViewModel<VideoPlayerViewModelImpl>()
|
viewModel: VideoPlayerViewModel = hiltViewModel<VideoPlayerViewModelImpl>()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
var lifecycle by remember {
|
var lifecycle by remember {
|
||||||
mutableStateOf(Lifecycle.Event.ON_CREATE)
|
mutableStateOf(Lifecycle.Event.ON_CREATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
DisposableEffect(lifecycleOwner) {
|
DisposableEffect(lifecycleOwner) {
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
|
@ -76,22 +80,32 @@ fun VideoPlayerUI(
|
||||||
}, update = {
|
}, update = {
|
||||||
when (lifecycle) {
|
when (lifecycle) {
|
||||||
Lifecycle.Event.ON_PAUSE -> {
|
Lifecycle.Event.ON_PAUSE -> {
|
||||||
|
it.onPause()
|
||||||
|
viewModel.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
Lifecycle.Event.ON_RESUME -> {
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
|
it.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
Lifecycle.Event.ON_START -> {
|
Lifecycle.Event.ON_START -> {
|
||||||
it.player?.play()
|
viewModel.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
VideoPlayerControllerUI()
|
VideoPlayerControllerUI(
|
||||||
|
isPlaying = viewModel.player?.isPlaying ?: false,
|
||||||
|
isFullscreen = uiState.fullscreen,
|
||||||
|
play = viewModel::play,
|
||||||
|
pause = viewModel::pause,
|
||||||
|
prevStream = viewModel::prevStream,
|
||||||
|
nextStream = viewModel::nextStream,
|
||||||
|
switchToFullscreen = viewModel::switchToFullscreen,
|
||||||
|
switchToEmbeddedView = viewModel::switchToEmbeddedView
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<string name="widget_description_next_stream">Previous stream</string>
|
<string name="widget_description_next_stream">Previous stream</string>
|
||||||
<string name="widget_description_play">Play</string>
|
<string name="widget_description_play">Play</string>
|
||||||
<string name="widget_description_pause">Pause</string>
|
<string name="widget_description_pause">Pause</string>
|
||||||
<string name="widget_description_fullscreen">Fullscreen</string>
|
<string name="widget_description_toggle_fullscreen">Toggle fullscreen</string>
|
||||||
<string name="widget_description_chapter_selection">Chapter selection</string>
|
<string name="widget_description_chapter_selection">Chapter selection</string>
|
||||||
<string name="widget_descriptoin_playlist_item_selection">Playlist item selection</string>
|
<string name="widget_descriptoin_playlist_item_selection">Playlist item selection</string>
|
||||||
</resources>
|
</resources>
|
|
@ -37,6 +37,7 @@ hiltCompiler = "1.2.0"
|
||||||
hiltNavigationCompose = "1.2.0"
|
hiltNavigationCompose = "1.2.0"
|
||||||
lifecycleViewmodelCompose = "2.8.3"
|
lifecycleViewmodelCompose = "2.8.3"
|
||||||
kspVersion = "1.9.0-1.0.13"
|
kspVersion = "1.9.0-1.0.13"
|
||||||
|
runtimeLivedata = "1.7.0-beta04"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
@ -59,6 +60,7 @@ androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navig
|
||||||
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hiltAndroid" }
|
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hiltAndroid" }
|
||||||
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
||||||
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "uiTooling" }
|
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "uiTooling" }
|
||||||
|
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
Loading…
Reference in New Issue