diff --git a/Jetcaster/core/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt b/Jetcaster/core/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt index e0e91040fe..1002127c98 100644 --- a/Jetcaster/core/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt +++ b/Jetcaster/core/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt @@ -31,7 +31,7 @@ fun HtmlTextContainer( text: String, content: @Composable (AnnotatedString) -> Unit ) { - val annotatedString = remember(key1 = text) { + val annotatedString = remember(text) { AnnotatedString.fromHtml(htmlString = text) } SelectionContainer { diff --git a/Jetcaster/glancewidget/src/main/java/com/example/jetcaster/glancewidget/JetcasterAppWidget.kt b/Jetcaster/glancewidget/src/main/java/com/example/jetcaster/glancewidget/JetcasterAppWidget.kt index 0fa21520e8..d39f0064fd 100644 --- a/Jetcaster/glancewidget/src/main/java/com/example/jetcaster/glancewidget/JetcasterAppWidget.kt +++ b/Jetcaster/glancewidget/src/main/java/com/example/jetcaster/glancewidget/JetcasterAppWidget.kt @@ -255,7 +255,7 @@ private fun WidgetAsyncImage( val context = LocalContext.current val scope = rememberCoroutineScope() - LaunchedEffect(key1 = uri) { + LaunchedEffect(uri) { val request = ImageRequest.Builder(context) .data(uri) .size(200, 200) diff --git a/Jetcaster/gradle/libs.versions.toml b/Jetcaster/gradle/libs.versions.toml index e72f661e58..6ec303d620 100644 --- a/Jetcaster/gradle/libs.versions.toml +++ b/Jetcaster/gradle/libs.versions.toml @@ -4,56 +4,56 @@ ##### [versions] accompanist = "0.36.0" -androidGradlePlugin = "8.7.2" +androidGradlePlugin = "8.7.3" androidx-activity-compose = "1.9.3" androidx-appcompat = "1.7.0" -androidx-benchmark = "1.2.4" -androidx-benchmark-junit4 = "1.2.4" -androidx-compose-bom = "2024.10.01" -androidx-constraintlayout = "1.1.0-alpha13" +androidx-benchmark = "1.3.3" +androidx-benchmark-junit4 = "1.3.3" +androidx-compose-bom = "2024.12.01" +androidx-constraintlayout = "1.1.0" androidx-core-splashscreen = "1.0.1" androidx-corektx = "1.15.0" -androidx-glance = "1.1.0" +androidx-glance = "1.1.1" androidx-lifecycle = "2.8.2" androidx-lifecycle-compose = "2.8.7" androidx-lifecycle-runtime-compose = "2.8.7" -androidx-navigation = "2.8.3" +androidx-navigation = "2.8.5" androidx-palette = "1.0.0" androidx-test = "1.6.1" androidx-test-espresso = "3.6.1" androidx-test-ext-junit = "1.2.1" -androidx-test-ext-truth = "1.5.0" +androidx-test-ext-truth = "1.6.0" androidx-tv-foundation = "1.0.0-alpha11" androidx-tv-material = "1.0.0" -androidx-wear-compose = "1.3.1" +androidx-wear-compose = "1.4.0" androidx-window = "1.3.0" androidxHiltNavigationCompose = "1.2.0" androix-test-uiautomator = "2.3.0" -coil = "2.6.0" +coil = "2.7.0" # @keep compileSdk = "35" coroutines = "1.9.0" -google-maps = "18.2.0" +google-maps = "19.0.0" gradle-versions = "0.51.0" hilt = "2.51.1" hiltExt = "1.2.0" horologist = "0.6.19" # @pin When updating to AGP 7.4.0-alpha10 and up we can update this https://developer.android.com/studio/write/java8-support#library-desugaring-versions -jdkDesugar = "1.2.2" +jdkDesugar = "2.1.3" junit = "4.13.2" -kotlin = "2.0.21" +kotlin = "2.1.0" kotlinx-serialization-json = "1.7.3" kotlinx_immutable = "0.3.8" ksp = "2.0.20-1.0.24" maps-compose = "3.1.1" # @keep minSdk = "21" -okhttp = "4.11.0" -play-services-wearable = "18.1.0" +okhttp = "4.12.0" +play-services-wearable = "19.0.0" robolectric = "4.13" -roborazzi = "1.12.0" +roborazzi = "1.26.0" rome = "1.18.0" -room = "2.6.0" +room = "2.6.1" secrets = "2.0.1" # @keep targetSdk = "33" diff --git a/Jetcaster/gradle/wrapper/gradle-wrapper.properties b/Jetcaster/gradle/wrapper/gradle-wrapper.properties index 4e6e184aad..08ce2fcd86 100644 --- a/Jetcaster/gradle/wrapper/gradle-wrapper.properties +++ b/Jetcaster/gradle/wrapper/gradle-wrapper.properties @@ -14,6 +14,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/Home.kt index a3230edfe9..0a813b05fd 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -400,7 +400,7 @@ private fun HomeScreen( modifier: Modifier = Modifier ) { // Effect that changes the home category selection when there are no subscribed podcasts - LaunchedEffect(key1 = featuredPodcasts) { + LaunchedEffect(featuredPodcasts) { if (featuredPodcasts.isEmpty()) { onHomeAction(HomeAction.HomeCategorySelected(HomeCategory.Discover)) } diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/Background.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/Background.kt index 4cdd5ccb52..30c05b5399 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/Background.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/Background.kt @@ -70,7 +70,7 @@ private fun Background( ) { ImageBackgroundRadialGradientScrim( url = imageUrl, - colors = listOf(Color.Black, Color.Transparent), + colors = listOf(Color(0xE0000000), Color(0xB0000000)), modifier = modifier, ) } diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/ButtonWithIcon.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/ButtonWithIcon.kt index b5fa71653c..f72f456523 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/ButtonWithIcon.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/ButtonWithIcon.kt @@ -16,6 +16,7 @@ package com.example.jetcaster.tv.ui.component +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable @@ -36,10 +37,10 @@ internal fun ButtonWithIcon( modifier: Modifier = Modifier, scale: ButtonScale = ButtonDefaults.scale(), ) { - Button(onClick = onClick, modifier = modifier, scale = scale) { + Button(onClick = onClick, contentPadding = PaddingValues(start = 6.dp, end = 16.dp), modifier = modifier, scale = scale) { Icon( icon, - contentDescription = null, + contentDescription = null ) Spacer(modifier = Modifier.width(6.dp)) Text(text = label) diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeCard.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeCard.kt index ddde4bcb56..82f95ff0e3 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeCard.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeCard.kt @@ -16,6 +16,7 @@ package com.example.jetcaster.tv.ui.component +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -30,9 +31,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import androidx.tv.material3.Border import androidx.tv.material3.Card import androidx.tv.material3.CardDefaults -import androidx.tv.material3.CardScale import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import androidx.tv.material3.WideCardContainer @@ -72,8 +73,18 @@ private fun EpisodeThumbnail( Card( onClick = onClick, interactionSource = interactionSource, - scale = CardScale.None, + scale = CardDefaults.scale(scale = 0.85f, focusedScale = 1.0f), shape = CardDefaults.shape(RoundedCornerShape(12.dp)), + border = CardDefaults.border( + focusedBorder = Border( + border = BorderStroke( + 3.dp, + color = MaterialTheme.colorScheme.border + ), + inset = 3.dp, + shape = RoundedCornerShape(15.dp) + ) + ), modifier = modifier, ) { Thumbnail(episode = playerEpisode, size = JetcasterAppDefaults.thumbnailSize.episode) diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeDateAndDuration.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeDateAndDuration.kt index 0ce6dbeaf7..72a30b10cc 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeDateAndDuration.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/EpisodeDateAndDuration.kt @@ -37,7 +37,7 @@ internal fun EpisodeDataAndDuration( offsetDateTime: OffsetDateTime, duration: Duration, modifier: Modifier = Modifier, - style: TextStyle = MaterialTheme.typography.bodySmall, + style: TextStyle = MaterialTheme.typography.bodyMedium, ) { Text( text = stringResource( diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/PodcastCard.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/PodcastCard.kt index 3524cae812..a98639a2af 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/PodcastCard.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/component/PodcastCard.kt @@ -16,14 +16,16 @@ package com.example.jetcaster.tv.ui.component +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.tv.material3.Border import androidx.tv.material3.Card import androidx.tv.material3.CardDefaults -import androidx.tv.material3.CardScale +import androidx.tv.material3.MaterialTheme import androidx.tv.material3.StandardCardContainer import androidx.tv.material3.Text import com.example.jetcaster.core.model.PodcastInfo @@ -40,8 +42,18 @@ internal fun PodcastCard( Card( onClick = onClick, interactionSource = it, - scale = CardScale.None, - shape = CardDefaults.shape(RoundedCornerShape(12.dp)) + scale = CardDefaults.scale(scale = 0.9f, focusedScale = 1.0f), + shape = CardDefaults.shape(RoundedCornerShape(16.dp)), + border = CardDefaults.border( + focusedBorder = Border( + border = BorderStroke( + 3.dp, + color = MaterialTheme.colorScheme.border + ), + inset = 3.dp, + shape = RoundedCornerShape(19.dp) + ), + ) ) { Thumbnail( podcastInfo = podcastInfo, diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt index a0727cd559..55941f0007 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt @@ -87,7 +87,6 @@ fun DiscoverScreen( private fun CatalogWithCategorySelection( categoryInfoList: CategoryInfoList, podcastList: PodcastList, - selectedCategory: CategoryInfo, latestEpisodeList: EpisodeList, onPodcastSelected: (PodcastInfo) -> Unit, diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/player/PlayerScreen.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/player/PlayerScreen.kt index bf81680771..6e617d94d3 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/player/PlayerScreen.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/player/PlayerScreen.kt @@ -17,7 +17,7 @@ package com.example.jetcaster.tv.ui.player import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.focusable +import androidx.compose.foundation.focusGroup import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -40,15 +40,19 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp @@ -63,6 +67,7 @@ import com.example.jetcaster.core.player.EpisodePlayerState import com.example.jetcaster.core.player.model.PlayerEpisode import com.example.jetcaster.tv.R import com.example.jetcaster.tv.model.EpisodeList +import com.example.jetcaster.tv.ui.Screen import com.example.jetcaster.tv.ui.component.BackgroundContainer import com.example.jetcaster.tv.ui.component.EnqueueButton import com.example.jetcaster.tv.ui.component.EpisodeDetails @@ -76,10 +81,11 @@ import com.example.jetcaster.tv.ui.component.RewindButton import com.example.jetcaster.tv.ui.component.Seekbar import com.example.jetcaster.tv.ui.component.SkipButton import com.example.jetcaster.tv.ui.theme.JetcasterAppDefaults -import java.time.Duration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import java.time.Duration +@OptIn(ExperimentalComposeUiApi::class) @Composable fun PlayerScreen( backToHome: () -> Unit, @@ -106,12 +112,13 @@ fun PlayerScreen( rewind = playScreenViewModel::rewind, enqueue = playScreenViewModel::enqueue, playEpisode = playScreenViewModel::play, - showDetails = showDetails, + showDetails = showDetails ) } } } +@ExperimentalComposeUiApi @Composable private fun Player( episodePlayerState: EpisodePlayerState, @@ -127,7 +134,7 @@ private fun Player( modifier: Modifier = Modifier, autoStart: Boolean = true ) { - LaunchedEffect(key1 = autoStart) { + LaunchedEffect(autoStart) { if (autoStart && !episodePlayerState.isPlaying) { play() } @@ -155,6 +162,7 @@ private fun Player( } } +@ExperimentalComposeUiApi @OptIn(ExperimentalFoundationApi::class) @Composable private fun EpisodePlayerWithBackground( @@ -173,18 +181,11 @@ private fun EpisodePlayerWithBackground( playEpisode: (PlayerEpisode) -> Unit, modifier: Modifier = Modifier ) { - val episodePlayer = remember { FocusRequester() } - - LaunchedEffect(Unit) { - episodePlayer.requestFocus() - } - BackgroundContainer( playerEpisode = playerEpisode, modifier = modifier, contentAlignment = Alignment.Center ) { - EpisodePlayer( playerEpisode = playerEpisode, isPlaying = isPlaying, @@ -197,7 +198,6 @@ private fun EpisodePlayerWithBackground( rewind = rewind, enqueue = enqueue, showDetails = showDetails, - focusRequester = episodePlayer, modifier = Modifier .padding(JetcasterAppDefaults.overScanMargin.player.intoPaddingValues()) ) @@ -213,6 +213,7 @@ private fun EpisodePlayerWithBackground( } } +@ExperimentalComposeUiApi @OptIn(ExperimentalFoundationApi::class) @Composable private fun EpisodePlayer( @@ -229,8 +230,7 @@ private fun EpisodePlayer( showDetails: (PlayerEpisode) -> Unit, modifier: Modifier = Modifier, bringIntoViewRequester: BringIntoViewRequester = remember { BringIntoViewRequester() }, - coroutineScope: CoroutineScope = rememberCoroutineScope(), - focusRequester: FocusRequester = remember { FocusRequester() } + coroutineScope: CoroutineScope = rememberCoroutineScope() ) { Column( verticalArrangement = Arrangement.spacedBy(JetcasterAppDefaults.gap.section), @@ -264,8 +264,7 @@ private fun EpisodePlayer( previous = previous, next = next, skip = skip, - rewind = rewind, - focusRequester = focusRequester + rewind = rewind ) } } @@ -291,6 +290,7 @@ private fun EpisodeControl( } } +@ExperimentalComposeUiApi @Composable private fun PlayerControl( isPlaying: Boolean, @@ -302,8 +302,7 @@ private fun PlayerControl( next: () -> Unit, skip: () -> Unit, rewind: () -> Unit, - modifier: Modifier = Modifier, - focusRequester: FocusRequester = remember { FocusRequester() } + modifier: Modifier = Modifier ) { val playPauseButton = remember { FocusRequester() } @@ -318,14 +317,13 @@ private fun PlayerControl( ), verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester) - .onFocusChanged { - if (it.isFocused) { - playPauseButton.requestFocus() + .focusProperties { + enter = { + playPauseButton } } - .focusable(), + .focusGroup() + .fillMaxWidth(), ) { PreviousButton( onClick = previous, diff --git a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastDetailsScreen.kt b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastDetailsScreen.kt index 26e84b7dc8..0d9e0a7f9e 100644 --- a/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastDetailsScreen.kt +++ b/Jetcaster/tv/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastDetailsScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape @@ -201,13 +202,16 @@ private fun PodcastInfo( ) Text( text = podcastInfo.title, + maxLines = 2, style = MaterialTheme.typography.headlineSmall, ) Text( text = podcastInfo.description, - maxLines = 2, + maxLines = 3, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier + .padding(top = JetcasterAppDefaults.gap.paragraph) ) ToggleSubscriptionButton( podcastInfo, @@ -283,8 +287,8 @@ private fun EpisodeListItem( onInfoClicked: () -> Unit, onEnqueueClicked: () -> Unit, modifier: Modifier = Modifier, - borderWidth: Dp = 2.dp, - cornerRadius: Dp = 12.dp, + borderWidth: Dp = 3.dp, + cornerRadius: Dp = 32.dp, ) { var hasFocus by remember { mutableStateOf(false) @@ -321,7 +325,7 @@ private fun EpisodeListItem( .border(borderWidth, borderColor, shape) .background(backgroundColor) .shadow(elevation, shape) - .padding(start = 12.dp, top = 12.dp, bottom = 12.dp, end = 16.dp) + .padding(start = 18.dp, top = 16.dp, bottom = 16.dp, end = 16.dp) ) } @@ -339,7 +343,6 @@ private fun EpisodeListItemContentLayer( contentAlignment = Alignment.CenterStart, modifier = modifier ) { - Column( verticalArrangement = Arrangement.spacedBy(JetcasterAppDefaults.gap.tiny), ) { @@ -352,14 +355,14 @@ private fun EpisodeListItemContentLayer( ) { PlayButton( onClick = onEpisodeSelected, - modifier = Modifier.focusRequester(playButton) + modifier = Modifier.focusRequester(playButton).height(36.dp) ) if (duration != null) { EpisodeDataAndDuration(playerEpisode.published, duration) } Spacer(modifier = Modifier.weight(1f)) - EnqueueButton(onClick = onEnqueueClicked) - InfoButton(onClick = onInfoClicked) + EnqueueButton(onClick = onEnqueueClicked, modifier = Modifier.size(36.dp)) + InfoButton(onClick = onInfoClicked, modifier = Modifier.size(36.dp)) } } }