From 570d1336eca9abd830330671f1988561b03bfdd3 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 4 Oct 2024 15:44:32 +0200 Subject: [PATCH 01/12] Some UI polish --- .../com/example/jetcaster/ui/home/Home.kt | 2 +- .../jetcaster/tv/ui/component/Background.kt | 2 +- .../tv/ui/component/ButtonWithIcon.kt | 5 +++-- .../jetcaster/tv/ui/component/EpisodeCard.kt | 14 +++++++++++-- .../tv/ui/component/EpisodeDateAndDuration.kt | 2 +- .../jetcaster/tv/ui/component/PodcastCard.kt | 17 ++++++++++++--- .../tv/ui/podcast/PodcastDetailsScreen.kt | 21 +++++++++++-------- 7 files changed, 44 insertions(+), 19 deletions(-) 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 46a0cffbf0..f28a556523 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 @@ -383,7 +383,7 @@ private fun HomeAppBar( modifier = modifier .fillMaxWidth() .background(Color.Transparent) - .padding(end = 16.dp, top = 8.dp, bottom = 8.dp) + .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp) ) { SearchBar( query = "", 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..75a6cf7f12 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 @@ -30,9 +30,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 +72,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 = _root_ide_package_.androidx.compose.foundation.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..ac6bb4902c 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 @@ -21,9 +21,10 @@ 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 +41,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 = _root_ide_package_.androidx.compose.foundation.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/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)) } } } From f544f49367e402aaaae2f2a90b5f797f54076da6 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 4 Oct 2024 15:44:54 +0200 Subject: [PATCH 02/12] Code style --- .../java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt | 1 - .../java/com/example/jetcaster/tv/ui/player/PlayerScreen.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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..66efdc77db 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 @@ -127,7 +127,7 @@ private fun Player( modifier: Modifier = Modifier, autoStart: Boolean = true ) { - LaunchedEffect(key1 = autoStart) { + LaunchedEffect(autoStart) { if (autoStart && !episodePlayerState.isPlaying) { play() } From ad13454a3dc4d0f54f20edc6f5730731368e9f12 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 4 Oct 2024 15:45:06 +0200 Subject: [PATCH 03/12] Gradle wrapper and dependencies --- Jetcaster/gradle/libs.versions.toml | 20 +++++++++---------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Jetcaster/gradle/libs.versions.toml b/Jetcaster/gradle/libs.versions.toml index 437b2dfe61..4eec658b77 100644 --- a/Jetcaster/gradle/libs.versions.toml +++ b/Jetcaster/gradle/libs.versions.toml @@ -7,10 +7,10 @@ accompanist = "0.36.0" androidGradlePlugin = "8.6.1" androidx-activity-compose = "1.9.2" androidx-appcompat = "1.7.0" -androidx-benchmark = "1.2.4" -androidx-benchmark-junit4 = "1.2.4" +androidx-benchmark = "1.3.1" +androidx-benchmark-junit4 = "1.3.1" androidx-compose-bom = "2024.09.02" -androidx-constraintlayout = "1.1.0-alpha13" +androidx-constraintlayout = "1.1.0-beta01" androidx-core-splashscreen = "1.0.1" androidx-corektx = "1.13.1" androidx-glance = "1.1.0" @@ -22,10 +22,10 @@ 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" @@ -33,13 +33,13 @@ coil = "2.6.0" # @keep compileSdk = "34" 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.2" junit = "4.13.2" kotlin = "2.0.20" kotlinx_immutable = "0.3.8" @@ -47,12 +47,12 @@ 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 = "18.2.0" robolectric = "4.13" roborazzi = "1.12.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 7c327a73f0..4e6e184aad 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.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a7db73a1553c664eb5d0e45f0a82124ce7577947 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 4 Oct 2024 15:45:40 +0200 Subject: [PATCH 04/12] Merged PR #1466 --- .../com/example/jetcaster/ui/home/Home.kt | 14 +++++++++---- .../jetcaster/ui/home/HomeViewModel.kt | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) 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 f28a556523..2fcb9d7922 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 @@ -83,6 +83,7 @@ import androidx.compose.material3.adaptive.occludingVerticalHingeBounds import androidx.compose.material3.adaptive.separatingVerticalHingeBounds import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -378,6 +379,11 @@ private fun HomeAppBar( isExpanded: Boolean, modifier: Modifier = Modifier, ) { + val viewModel: HomeViewModel = hiltViewModel() + + val searchText by viewModel.searchText.collectAsState() + val isSearching by viewModel.isSearching.collectAsState() + Row( horizontalArrangement = Arrangement.End, modifier = modifier @@ -386,14 +392,14 @@ private fun HomeAppBar( .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp) ) { SearchBar( - query = "", - onQueryChange = {}, + query = searchText, + onQueryChange = { viewModel.onSearchTextChange(it) }, placeholder = { Text(stringResource(id = R.string.search_for_a_podcast)) }, - onSearch = {}, + onSearch = { viewModel.onSearchTextChange(it) }, active = false, - onActiveChange = {}, + onActiveChange = { viewModel.onToggleSearch() }, leadingIcon = { Icon( imageVector = Icons.Default.Search, diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt index 3d5450464d..2e0422140a 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt @@ -43,6 +43,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.shareIn @@ -72,6 +73,14 @@ class HomeViewModel @Inject constructor( // Holds the view state if the UI is refreshing for new data private val refreshing = MutableStateFlow(false) + //first state whether the search is happening or not + private val _isSearching = MutableStateFlow(false) + val isSearching = _isSearching.asStateFlow() + + //second state the text typed by the user + private val _searchText = MutableStateFlow("") + val searchText = _searchText.asStateFlow() + private val subscribedPodcasts = podcastStore.followedPodcastsSortedByLastEpisode(limit = 10) .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) @@ -149,6 +158,17 @@ class HomeViewModel @Inject constructor( } } + fun onSearchTextChange(text: String) { + _searchText.value = text + } + + fun onToggleSearch() { + _isSearching.value = !_isSearching.value + if (!_isSearching.value) { + onSearchTextChange("") + } + } + fun onCategorySelected(category: CategoryInfo) { _selectedCategory.value = category } From 6ec02a43020d28238740e7b0c31eeba0432c2473 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 4 Oct 2024 16:12:18 +0200 Subject: [PATCH 05/12] Code style --- .../jetcaster/designsystem/component/HtmlTextContainer.kt | 2 +- .../com/example/jetcaster/glancewidget/JetcasterAppWidget.kt | 2 +- .../mobile/src/main/java/com/example/jetcaster/ui/home/Home.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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 2fcb9d7922..5b626e675e 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 @@ -442,7 +442,7 @@ private fun HomeScreen( modifier: Modifier = Modifier ) { // Effect that changes the home category selection when there are no subscribed podcasts - LaunchedEffect(key1 = homeState.featuredPodcasts) { + LaunchedEffect(homeState.featuredPodcasts) { if (homeState.featuredPodcasts.isEmpty()) { homeState.onHomeCategorySelected(HomeCategory.Discover) } From 3d67f91bc65e97adbd096c0f9dfcf7d6876a2ba6 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 4 Oct 2024 16:12:41 +0200 Subject: [PATCH 06/12] Resolved focus traversal and back navigation issue --- .../jetcaster/tv/ui/player/PlayerScreen.kt | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) 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 66efdc77db..3cdf336653 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 @@ -34,21 +34,25 @@ import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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.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 @@ -80,6 +84,7 @@ import java.time.Duration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +@OptIn(ExperimentalComposeUiApi::class) @Composable fun PlayerScreen( backToHome: () -> Unit, @@ -106,12 +111,13 @@ fun PlayerScreen( rewind = playScreenViewModel::rewind, enqueue = playScreenViewModel::enqueue, playEpisode = playScreenViewModel::play, - showDetails = showDetails, + showDetails = showDetails ) } } } +@ExperimentalComposeUiApi @Composable private fun Player( episodePlayerState: EpisodePlayerState, @@ -155,6 +161,7 @@ private fun Player( } } +@ExperimentalComposeUiApi @OptIn(ExperimentalFoundationApi::class) @Composable private fun EpisodePlayerWithBackground( @@ -173,18 +180,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 +197,6 @@ private fun EpisodePlayerWithBackground( rewind = rewind, enqueue = enqueue, showDetails = showDetails, - focusRequester = episodePlayer, modifier = Modifier .padding(JetcasterAppDefaults.overScanMargin.player.intoPaddingValues()) ) @@ -213,6 +212,7 @@ private fun EpisodePlayerWithBackground( } } +@ExperimentalComposeUiApi @OptIn(ExperimentalFoundationApi::class) @Composable private fun EpisodePlayer( @@ -229,8 +229,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 +263,7 @@ private fun EpisodePlayer( previous = previous, next = next, skip = skip, - rewind = rewind, - focusRequester = focusRequester + rewind = rewind ) } } @@ -291,6 +289,7 @@ private fun EpisodeControl( } } +@ExperimentalComposeUiApi @Composable private fun PlayerControl( isPlaying: Boolean, @@ -302,11 +301,13 @@ private fun PlayerControl( next: () -> Unit, skip: () -> Unit, rewind: () -> Unit, - modifier: Modifier = Modifier, - focusRequester: FocusRequester = remember { FocusRequester() } + modifier: Modifier = Modifier ) { val playPauseButton = remember { FocusRequester() } + // Flag to determine if it is safe to set initial focus or not + var isSafeToSetInitialFocus by remember { mutableStateOf(false) } + Column( verticalArrangement = Arrangement.spacedBy(JetcasterAppDefaults.gap.item), modifier = modifier, @@ -318,14 +319,9 @@ private fun PlayerControl( ), verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester) - .onFocusChanged { - if (it.isFocused) { - playPauseButton.requestFocus() - } - } - .focusable(), + .focusGroup() + .focusRestorer() + .fillMaxWidth(), ) { PreviousButton( onClick = previous, @@ -347,6 +343,10 @@ private fun PlayerControl( modifier = Modifier .size(JetcasterAppDefaults.iconButtonSize.large.intoDpSize()) .focusRequester(playPauseButton) + .onGloballyPositioned { + // Set the flag true if the element is in the viewport + isSafeToSetInitialFocus = true + } ) SkipButton( onClick = skip, @@ -361,6 +361,12 @@ private fun PlayerControl( ElapsedTimeIndicator(timeElapsed, length, skip, rewind) } } + LaunchedEffect(isSafeToSetInitialFocus) { + // Your app should set initial focus only if the UI element is in viewport + if (isSafeToSetInitialFocus) { + playPauseButton.requestFocus() + } + } } @Composable From cf46ae85e926f93c1a49081f5fe777f254f99471 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 4 Oct 2024 16:15:31 +0200 Subject: [PATCH 07/12] Ensure that the play/pause button is always focused when entering the focus group --- .../example/jetcaster/tv/ui/player/PlayerScreen.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 3cdf336653..5f210d000b 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 @@ -34,7 +34,6 @@ import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -45,6 +44,7 @@ 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 @@ -67,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 @@ -80,9 +81,9 @@ 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 @@ -319,8 +320,12 @@ private fun PlayerControl( ), verticalAlignment = Alignment.CenterVertically, modifier = Modifier + .focusProperties { + enter = { + playPauseButton + } + } .focusGroup() - .focusRestorer() .fillMaxWidth(), ) { PreviousButton( From 973b3f1e914b1febd68459ec8775f147b920e749 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Wed, 18 Dec 2024 23:53:42 +0100 Subject: [PATCH 08/12] removed stale remainder of PR #1466 that was unused --- .../src/main/java/com/example/jetcaster/ui/home/Home.kt | 1 - .../java/com/example/jetcaster/ui/home/HomeViewModel.kt | 9 --------- 2 files changed, 10 deletions(-) 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 8f1c4b3708..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 @@ -82,7 +82,6 @@ import androidx.compose.material3.adaptive.occludingVerticalHingeBounds import androidx.compose.material3.adaptive.separatingVerticalHingeBounds import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt index e8e4e86c17..f84b68cd32 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt @@ -43,7 +43,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.shareIn @@ -77,14 +76,6 @@ class HomeViewModel @Inject constructor( // Holds the view state if the UI is refreshing for new data private val refreshing = MutableStateFlow(false) - //first state whether the search is happening or not - private val _isSearching = MutableStateFlow(false) - val isSearching = _isSearching.asStateFlow() - - //second state the text typed by the user - private val _searchText = MutableStateFlow("") - val searchText = _searchText.asStateFlow() - private val subscribedPodcasts = podcastStore.followedPodcastsSortedByLastEpisode(limit = 10) .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) From 2a50087eef6d222d9c15a9a9acf2ae1e4bffd1e3 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Wed, 18 Dec 2024 23:54:54 +0100 Subject: [PATCH 09/12] Fixed import --- .../java/com/example/jetcaster/tv/ui/component/PodcastCard.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ac6bb4902c..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,6 +16,7 @@ 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 @@ -45,7 +46,7 @@ internal fun PodcastCard( shape = CardDefaults.shape(RoundedCornerShape(16.dp)), border = CardDefaults.border( focusedBorder = Border( - border = _root_ide_package_.androidx.compose.foundation.BorderStroke( + border = BorderStroke( 3.dp, color = MaterialTheme.colorScheme.border ), From 471f2799f4a1f4d1b3079672dd3d2afce13d4a6a Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Wed, 18 Dec 2024 23:55:40 +0100 Subject: [PATCH 10/12] Gradle wrapper and dependencies --- Jetcaster/gradle/libs.versions.toml | 24 +++++++++---------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Jetcaster/gradle/libs.versions.toml b/Jetcaster/gradle/libs.versions.toml index 641f516c20..6ec303d620 100644 --- a/Jetcaster/gradle/libs.versions.toml +++ b/Jetcaster/gradle/libs.versions.toml @@ -4,20 +4,20 @@ ##### [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.3.1" -androidx-benchmark-junit4 = "1.3.1" -androidx-compose-bom = "2024.10.01" -androidx-constraintlayout = "1.1.0-beta01" +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" @@ -29,7 +29,7 @@ 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" @@ -39,9 +39,9 @@ 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 = "2.1.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" @@ -49,9 +49,9 @@ maps-compose = "3.1.1" # @keep minSdk = "21" okhttp = "4.12.0" -play-services-wearable = "18.2.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.1" secrets = "2.0.1" 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 From 15dc262865b44c0e1c61b4d5b92294c906900b91 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Wed, 18 Dec 2024 23:56:39 +0100 Subject: [PATCH 11/12] Fixed import --- .../java/com/example/jetcaster/tv/ui/component/EpisodeCard.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 75a6cf7f12..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 @@ -76,7 +77,7 @@ private fun EpisodeThumbnail( shape = CardDefaults.shape(RoundedCornerShape(12.dp)), border = CardDefaults.border( focusedBorder = Border( - border = _root_ide_package_.androidx.compose.foundation.BorderStroke( + border = BorderStroke( 3.dp, color = MaterialTheme.colorScheme.border ), From cebdfb78194b9aadbe99d382cc9bd7315f17f6f4 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Wed, 18 Dec 2024 23:59:44 +0100 Subject: [PATCH 12/12] Fixed import --- .../example/jetcaster/tv/ui/player/PlayerScreen.kt | 13 ------------- 1 file changed, 13 deletions(-) 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 5f210d000b..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 @@ -306,9 +306,6 @@ private fun PlayerControl( ) { val playPauseButton = remember { FocusRequester() } - // Flag to determine if it is safe to set initial focus or not - var isSafeToSetInitialFocus by remember { mutableStateOf(false) } - Column( verticalArrangement = Arrangement.spacedBy(JetcasterAppDefaults.gap.item), modifier = modifier, @@ -348,10 +345,6 @@ private fun PlayerControl( modifier = Modifier .size(JetcasterAppDefaults.iconButtonSize.large.intoDpSize()) .focusRequester(playPauseButton) - .onGloballyPositioned { - // Set the flag true if the element is in the viewport - isSafeToSetInitialFocus = true - } ) SkipButton( onClick = skip, @@ -366,12 +359,6 @@ private fun PlayerControl( ElapsedTimeIndicator(timeElapsed, length, skip, rewind) } } - LaunchedEffect(isSafeToSetInitialFocus) { - // Your app should set initial focus only if the UI element is in viewport - if (isSafeToSetInitialFocus) { - playPauseButton.requestFocus() - } - } } @Composable