update
This commit is contained in:
@@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
|
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:banner="@drawable/tv_banner"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:label="YouTube App"
|
android:label="YouTube App"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
@@ -15,6 +19,7 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.youtubeapp.ui.components
|
package com.youtubeapp.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -8,7 +10,13 @@ import androidx.compose.material3.Card
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
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.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -17,9 +25,18 @@ import com.youtubeapp.data.Video
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoCard(video: Video, onClick: () -> Unit) {
|
fun VideoCard(video: Video, onClick: () -> Unit) {
|
||||||
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.onFocusChanged { isFocused = it.isFocused }
|
||||||
|
.then(
|
||||||
|
if (isFocused) Modifier.border(3.dp, Color.White, MaterialTheme.shapes.medium)
|
||||||
|
else Modifier
|
||||||
|
)
|
||||||
|
.focusable()
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ fun AllVideosScreen(viewModel: VideoViewModel, onVideoClick: (Int) -> Unit) {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(2),
|
columns = GridCells.Adaptive(minSize = 250.dp),
|
||||||
contentPadding = PaddingValues(8.dp),
|
contentPadding = PaddingValues(8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ fun DownloadedScreen(viewModel: VideoViewModel, onVideoClick: (Int) -> Unit) {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(2),
|
columns = GridCells.Adaptive(minSize = 250.dp),
|
||||||
contentPadding = PaddingValues(8.dp),
|
contentPadding = PaddingValues(8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.youtubeapp.ui.screens
|
package com.youtubeapp.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -73,19 +74,38 @@ fun VideoDetailScreen(
|
|||||||
if (video == null) {
|
if (video == null) {
|
||||||
Text("Video nicht gefunden", modifier = Modifier.padding(innerPadding).padding(16.dp))
|
Text("Video nicht gefunden", modifier = Modifier.padding(innerPadding).padding(16.dp))
|
||||||
} else {
|
} else {
|
||||||
Column(
|
BoxWithConstraints(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
) {
|
) {
|
||||||
|
val isWide = maxWidth > 600.dp
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = video.thumbnail_url,
|
model = video.thumbnail_url,
|
||||||
contentDescription = video.title,
|
contentDescription = video.title,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(if (isWide) 0.5f else 1f)
|
||||||
.aspectRatio(16f / 9f)
|
.aspectRatio(16f / 9f)
|
||||||
)
|
)
|
||||||
|
VideoInfo(video, isLocal, state.isDownloading, viewModel, videoId, onPlayClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun VideoInfo(
|
||||||
|
video: com.youtubeapp.data.Video,
|
||||||
|
isLocal: Boolean,
|
||||||
|
isDownloading: Boolean,
|
||||||
|
viewModel: VideoViewModel,
|
||||||
|
videoId: Int,
|
||||||
|
onPlayClick: () -> Unit
|
||||||
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = video.title,
|
text = video.title,
|
||||||
@@ -119,7 +139,7 @@ fun VideoDetailScreen(
|
|||||||
Icon(Icons.Default.PlayArrow, contentDescription = null)
|
Icon(Icons.Default.PlayArrow, contentDescription = null)
|
||||||
Text(" Abspielen")
|
Text(" Abspielen")
|
||||||
}
|
}
|
||||||
if (state.isDownloading) {
|
if (isDownloading) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {},
|
onClick = {},
|
||||||
enabled = false,
|
enabled = false,
|
||||||
@@ -150,7 +170,4 @@ fun VideoDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.youtubeapp.ui.screens
|
package com.youtubeapp.ui.screens
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.view.LayoutInflater
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -14,6 +15,7 @@ import androidx.core.view.WindowInsetsControllerCompat
|
|||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
|
import com.youtubeapp.R
|
||||||
import com.youtubeapp.ui.viewmodel.VideoViewModel
|
import com.youtubeapp.ui.viewmodel.VideoViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -49,10 +51,12 @@ fun VideoPlayerScreen(videoId: Int, viewModel: VideoViewModel) {
|
|||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = { ctx ->
|
factory = { ctx ->
|
||||||
PlayerView(ctx).apply {
|
val view = LayoutInflater.from(ctx).inflate(R.layout.player_view, null) as PlayerView
|
||||||
player = exoPlayer
|
view.player = exoPlayer
|
||||||
useController = true
|
view
|
||||||
}
|
},
|
||||||
|
update = { view ->
|
||||||
|
view.player = exoPlayer
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
|||||||
BIN
app/frontend/src/main/res/drawable/tv_banner.png
Normal file
BIN
app/frontend/src/main/res/drawable/tv_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 942 B |
10
app/frontend/src/main/res/layout/player_view.xml
Normal file
10
app/frontend/src/main/res/layout/player_view.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.media3.ui.PlayerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/player_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:surface_type="texture_view"
|
||||||
|
app:show_buffering="when_playing"
|
||||||
|
app:use_controller="true" />
|
||||||
Reference in New Issue
Block a user