diff --git a/app/frontend/src/main/AndroidManifest.xml b/app/frontend/src/main/AndroidManifest.xml
index 5218c6a..fc6e766 100644
--- a/app/frontend/src/main/AndroidManifest.xml
+++ b/app/frontend/src/main/AndroidManifest.xml
@@ -3,7 +3,11 @@
+
+
+
+
diff --git a/app/frontend/src/main/java/com/youtubeapp/ui/components/VideoCard.kt b/app/frontend/src/main/java/com/youtubeapp/ui/components/VideoCard.kt
index a54c84e..ab90037 100644
--- a/app/frontend/src/main/java/com/youtubeapp/ui/components/VideoCard.kt
+++ b/app/frontend/src/main/java/com/youtubeapp/ui/components/VideoCard.kt
@@ -1,5 +1,7 @@
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.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
@@ -8,7 +10,13 @@ import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
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.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -17,9 +25,18 @@ import com.youtubeapp.data.Video
@Composable
fun VideoCard(video: Video, onClick: () -> Unit) {
+ var isFocused by remember { mutableStateOf(false) }
+
Card(
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 {
AsyncImage(
diff --git a/app/frontend/src/main/java/com/youtubeapp/ui/screens/AllVideosScreen.kt b/app/frontend/src/main/java/com/youtubeapp/ui/screens/AllVideosScreen.kt
index e2754f9..95befbb 100644
--- a/app/frontend/src/main/java/com/youtubeapp/ui/screens/AllVideosScreen.kt
+++ b/app/frontend/src/main/java/com/youtubeapp/ui/screens/AllVideosScreen.kt
@@ -47,7 +47,7 @@ fun AllVideosScreen(viewModel: VideoViewModel, onVideoClick: (Int) -> Unit) {
}
else -> {
LazyVerticalGrid(
- columns = GridCells.Fixed(2),
+ columns = GridCells.Adaptive(minSize = 250.dp),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
diff --git a/app/frontend/src/main/java/com/youtubeapp/ui/screens/DownloadedScreen.kt b/app/frontend/src/main/java/com/youtubeapp/ui/screens/DownloadedScreen.kt
index 70c5e3c..589c8a2 100644
--- a/app/frontend/src/main/java/com/youtubeapp/ui/screens/DownloadedScreen.kt
+++ b/app/frontend/src/main/java/com/youtubeapp/ui/screens/DownloadedScreen.kt
@@ -52,7 +52,7 @@ fun DownloadedScreen(viewModel: VideoViewModel, onVideoClick: (Int) -> Unit) {
}
else -> {
LazyVerticalGrid(
- columns = GridCells.Fixed(2),
+ columns = GridCells.Adaptive(minSize = 250.dp),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
diff --git a/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoDetailScreen.kt b/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoDetailScreen.kt
index 256f535..05e73aa 100644
--- a/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoDetailScreen.kt
+++ b/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoDetailScreen.kt
@@ -1,6 +1,7 @@
package com.youtubeapp.ui.screens
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -73,82 +74,98 @@ fun VideoDetailScreen(
if (video == null) {
Text("Video nicht gefunden", modifier = Modifier.padding(innerPadding).padding(16.dp))
} else {
- Column(
+ BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
- AsyncImage(
- model = video.thumbnail_url,
- contentDescription = video.title,
- contentScale = ContentScale.Crop,
- modifier = Modifier
- .fillMaxWidth()
- .aspectRatio(16f / 9f)
- )
- Column(modifier = Modifier.padding(16.dp)) {
- Text(
- text = video.title,
- style = MaterialTheme.typography.headlineSmall
+ val isWide = maxWidth > 600.dp
+
+ Column(modifier = Modifier.fillMaxSize()) {
+ AsyncImage(
+ model = video.thumbnail_url,
+ contentDescription = video.title,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxWidth(if (isWide) 0.5f else 1f)
+ .aspectRatio(16f / 9f)
)
- Spacer(modifier = Modifier.height(4.dp))
- Text(
- text = video.youtuber,
- style = MaterialTheme.typography.bodyLarge,
- color = MaterialTheme.colorScheme.onSurfaceVariant
- )
- Spacer(modifier = Modifier.height(4.dp))
- Text(
- text = when {
- isLocal -> "Lokal gespeichert"
- video.is_downloaded -> "Auf Server heruntergeladen"
- else -> "Noch nicht heruntergeladen"
- },
- style = MaterialTheme.typography.bodySmall,
- color = MaterialTheme.colorScheme.onSurfaceVariant
- )
- Spacer(modifier = Modifier.height(16.dp))
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- modifier = Modifier.fillMaxWidth()
- ) {
- Button(
- onClick = onPlayClick,
- modifier = Modifier.weight(1f)
- ) {
- Icon(Icons.Default.PlayArrow, contentDescription = null)
- Text(" Abspielen")
- }
- if (state.isDownloading) {
- OutlinedButton(
- onClick = {},
- enabled = false,
- modifier = Modifier.weight(1f)
- ) {
- CircularProgressIndicator(
- modifier = Modifier.height(20.dp),
- strokeWidth = 2.dp
- )
- Text(" Download...")
- }
- } else if (isLocal) {
- OutlinedButton(
- onClick = { viewModel.deleteLocalVideo(videoId) },
- modifier = Modifier.weight(1f)
- ) {
- Icon(Icons.Default.Delete, contentDescription = null)
- Text(" Loeschen")
- }
- } else {
- OutlinedButton(
- onClick = { viewModel.triggerDownload(videoId) },
- modifier = Modifier.weight(1f)
- ) {
- Icon(Icons.Default.Download, contentDescription = null)
- Text(" Download")
- }
- }
- }
+ 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)) {
+ Text(
+ text = video.title,
+ style = MaterialTheme.typography.headlineSmall
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = video.youtuber,
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = when {
+ isLocal -> "Lokal gespeichert"
+ video.is_downloaded -> "Auf Server heruntergeladen"
+ else -> "Noch nicht heruntergeladen"
+ },
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Button(
+ onClick = onPlayClick,
+ modifier = Modifier.weight(1f)
+ ) {
+ Icon(Icons.Default.PlayArrow, contentDescription = null)
+ Text(" Abspielen")
+ }
+ if (isDownloading) {
+ OutlinedButton(
+ onClick = {},
+ enabled = false,
+ modifier = Modifier.weight(1f)
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier.height(20.dp),
+ strokeWidth = 2.dp
+ )
+ Text(" Download...")
+ }
+ } else if (isLocal) {
+ OutlinedButton(
+ onClick = { viewModel.deleteLocalVideo(videoId) },
+ modifier = Modifier.weight(1f)
+ ) {
+ Icon(Icons.Default.Delete, contentDescription = null)
+ Text(" Loeschen")
+ }
+ } else {
+ OutlinedButton(
+ onClick = { viewModel.triggerDownload(videoId) },
+ modifier = Modifier.weight(1f)
+ ) {
+ Icon(Icons.Default.Download, contentDescription = null)
+ Text(" Download")
}
}
}
diff --git a/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoPlayerScreen.kt b/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoPlayerScreen.kt
index a3ad770..622e2f6 100644
--- a/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoPlayerScreen.kt
+++ b/app/frontend/src/main/java/com/youtubeapp/ui/screens/VideoPlayerScreen.kt
@@ -1,6 +1,7 @@
package com.youtubeapp.ui.screens
import android.app.Activity
+import android.view.LayoutInflater
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -14,6 +15,7 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
+import com.youtubeapp.R
import com.youtubeapp.ui.viewmodel.VideoViewModel
@Composable
@@ -49,10 +51,12 @@ fun VideoPlayerScreen(videoId: Int, viewModel: VideoViewModel) {
AndroidView(
factory = { ctx ->
- PlayerView(ctx).apply {
- player = exoPlayer
- useController = true
- }
+ val view = LayoutInflater.from(ctx).inflate(R.layout.player_view, null) as PlayerView
+ view.player = exoPlayer
+ view
+ },
+ update = { view ->
+ view.player = exoPlayer
},
modifier = Modifier.fillMaxSize()
)
diff --git a/app/frontend/src/main/res/drawable/tv_banner.png b/app/frontend/src/main/res/drawable/tv_banner.png
new file mode 100644
index 0000000..453b2a9
Binary files /dev/null and b/app/frontend/src/main/res/drawable/tv_banner.png differ
diff --git a/app/frontend/src/main/res/layout/player_view.xml b/app/frontend/src/main/res/layout/player_view.xml
new file mode 100644
index 0000000..73d40e6
--- /dev/null
+++ b/app/frontend/src/main/res/layout/player_view.xml
@@ -0,0 +1,10 @@
+
+