From ddb1e5e28b0cc365a6e626bc00c92fa73bde58b6 Mon Sep 17 00:00:00 2001 From: hoangchungk53qx1 Date: Wed, 20 Aug 2025 19:37:55 +0700 Subject: [PATCH 1/4] Add stability configuration and apply @Immutable annotation to data classes --- .../core/data/database/model/EpisodeToPodcast.kt | 2 ++ .../example/jetcaster/core/model/CategoryInfo.kt | 2 ++ .../example/jetcaster/core/model/EpisodeInfo.kt | 3 +++ .../core/model/FilterableCategoriesModel.kt | 4 ++++ .../core/model/PodcastCategoryFilterResult.kt | 3 +++ .../example/jetcaster/core/model/PodcastInfo.kt | 2 ++ .../jetcaster/core/model/PodcastToEpisodeInfo.kt | 2 ++ .../example/jetcaster/core/player/EpisodePlayer.kt | 3 +++ .../jetcaster/core/player/model/PlayerEpisode.kt | 3 +++ Jetcaster/mobile/build.gradle.kts | 6 ++++++ .../java/com/example/jetcaster/ui/home/Home.kt | 14 ++++++++------ .../com/example/jetcaster/ui/home/HomeViewModel.kt | 4 ++-- .../ui/podcast/PodcastDetailsViewModel.kt | 2 ++ Jetcaster/stability_config.conf | 2 ++ Jetcaster/tv/build.gradle.kts | 6 ++++++ Jetcaster/wear/build.gradle | 10 ++++++++++ 16 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 Jetcaster/stability_config.conf diff --git a/Jetcaster/core/data/src/main/java/com/example/jetcaster/core/data/database/model/EpisodeToPodcast.kt b/Jetcaster/core/data/src/main/java/com/example/jetcaster/core/data/database/model/EpisodeToPodcast.kt index 7945f20316..8958d23ded 100644 --- a/Jetcaster/core/data/src/main/java/com/example/jetcaster/core/data/database/model/EpisodeToPodcast.kt +++ b/Jetcaster/core/data/src/main/java/com/example/jetcaster/core/data/database/model/EpisodeToPodcast.kt @@ -16,11 +16,13 @@ package com.example.jetcaster.core.data.database.model +import androidx.compose.runtime.Immutable import androidx.room.Embedded import androidx.room.Ignore import androidx.room.Relation import java.util.Objects +@Immutable class EpisodeToPodcast { @Embedded lateinit var episode: Episode diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/CategoryInfo.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/CategoryInfo.kt index f462e4cb90..7ef3687559 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/CategoryInfo.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/CategoryInfo.kt @@ -16,8 +16,10 @@ package com.example.jetcaster.core.model +import androidx.compose.runtime.Immutable import com.example.jetcaster.core.data.database.model.Category +@Immutable data class CategoryInfo(val id: Long, val name: String) const val CategoryTechnology = "Technology" diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/EpisodeInfo.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/EpisodeInfo.kt index 8bf70ed115..5fcb4226ea 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/EpisodeInfo.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/EpisodeInfo.kt @@ -16,6 +16,7 @@ package com.example.jetcaster.core.model +import androidx.compose.runtime.Immutable import com.example.jetcaster.core.data.database.model.Episode import java.time.Duration import java.time.OffsetDateTime @@ -23,6 +24,8 @@ import java.time.OffsetDateTime /** * External data layer representation of an episode. */ + +@Immutable data class EpisodeInfo( val uri: String = "", val podcastUri: String = "", diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/FilterableCategoriesModel.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/FilterableCategoriesModel.kt index a359437924..d8975dbbeb 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/FilterableCategoriesModel.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/FilterableCategoriesModel.kt @@ -16,9 +16,13 @@ package com.example.jetcaster.core.model +import androidx.compose.runtime.Immutable + /** * Model holding a list of categories and a selected category in the collection */ + +@Immutable data class FilterableCategoriesModel(val categories: List = emptyList(), val selectedCategory: CategoryInfo? = null) { val isEmpty = categories.isEmpty() || selectedCategory == null } diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastCategoryFilterResult.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastCategoryFilterResult.kt index 871780034d..2520a01d94 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastCategoryFilterResult.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastCategoryFilterResult.kt @@ -16,9 +16,12 @@ package com.example.jetcaster.core.model +import androidx.compose.runtime.Immutable + /** * A model holding top podcasts and matching episodes when filtering based on a category. */ +@Immutable data class PodcastCategoryFilterResult( val topPodcasts: List = emptyList(), val episodes: List = emptyList(), diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastInfo.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastInfo.kt index 2150c06ec6..7380c6f646 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastInfo.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastInfo.kt @@ -16,6 +16,7 @@ package com.example.jetcaster.core.model +import androidx.compose.runtime.Immutable import com.example.jetcaster.core.data.database.model.Podcast import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo import java.time.OffsetDateTime @@ -23,6 +24,7 @@ import java.time.OffsetDateTime /** * External data layer representation of a podcast. */ +@Immutable data class PodcastInfo( val uri: String = "", val title: String = "", diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastToEpisodeInfo.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastToEpisodeInfo.kt index 2b69d1fe4f..86aa1ccdea 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastToEpisodeInfo.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/model/PodcastToEpisodeInfo.kt @@ -16,8 +16,10 @@ package com.example.jetcaster.core.model +import androidx.compose.runtime.Immutable import com.example.jetcaster.core.data.database.model.EpisodeToPodcast +@Immutable data class PodcastToEpisodeInfo(val episode: EpisodeInfo, val podcast: PodcastInfo) fun EpisodeToPodcast.asPodcastToEpisodeInfo(): PodcastToEpisodeInfo = PodcastToEpisodeInfo( diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/EpisodePlayer.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/EpisodePlayer.kt index c01d64c61c..874c5e360d 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/EpisodePlayer.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/EpisodePlayer.kt @@ -16,11 +16,14 @@ package com.example.jetcaster.core.player +import androidx.compose.runtime.Immutable import com.example.jetcaster.core.player.model.PlayerEpisode import java.time.Duration import kotlinx.coroutines.flow.StateFlow val DefaultPlaybackSpeed = Duration.ofSeconds(1) + +@Immutable data class EpisodePlayerState( val currentEpisode: PlayerEpisode? = null, val queue: List = emptyList(), diff --git a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/model/PlayerEpisode.kt b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/model/PlayerEpisode.kt index adc1a21c09..f99f9a0393 100644 --- a/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/model/PlayerEpisode.kt +++ b/Jetcaster/core/domain/src/main/java/com/example/jetcaster/core/player/model/PlayerEpisode.kt @@ -16,6 +16,7 @@ package com.example.jetcaster.core.player.model +import androidx.compose.runtime.Immutable import com.example.jetcaster.core.data.database.model.EpisodeToPodcast import com.example.jetcaster.core.model.EpisodeInfo import com.example.jetcaster.core.model.PodcastInfo @@ -25,6 +26,8 @@ import java.time.OffsetDateTime /** * Episode data with necessary information to be used within a player. */ + +@Immutable data class PlayerEpisode( val uri: String = "", val title: String = "", diff --git a/Jetcaster/mobile/build.gradle.kts b/Jetcaster/mobile/build.gradle.kts index 469fbe8675..c24b53c16c 100644 --- a/Jetcaster/mobile/build.gradle.kts +++ b/Jetcaster/mobile/build.gradle.kts @@ -97,6 +97,12 @@ android { excludes += "/META-INF/AL2.0" excludes += "/META-INF/LGPL2.1" } + + composeCompiler { + reportsDestination = layout.buildDirectory.dir("compose_compiler") + metricsDestination = layout.buildDirectory.dir("compose_compiler") + stabilityConfigurationFiles = listOf(rootProject.layout.projectDirectory.file("stability_config.conf")) + } } dependencies { 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 15675e8a39..f49c2d850a 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 @@ -115,10 +115,12 @@ import com.example.jetcaster.util.fullWidthItem import com.example.jetcaster.util.isCompact import com.example.jetcaster.util.quantityStringResource import com.example.jetcaster.util.radialGradientScrim +import kotlinx.collections.immutable.ImmutableList import java.time.Duration import java.time.LocalDateTime import java.time.OffsetDateTime import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch @@ -373,7 +375,7 @@ private fun HomeScreenBackground(modifier: Modifier = Modifier, content: @Compos private fun HomeScreen( windowSizeClass: WindowSizeClass, isLoading: Boolean, - featuredPodcasts: PersistentList, + featuredPodcasts: ImmutableList, selectedHomeCategory: HomeCategory, homeCategories: List, filterableCategoriesModel: FilterableCategoriesModel, @@ -538,7 +540,7 @@ fun PillToolbar(selectedHomeCategory: HomeCategory, onHomeAction: (HomeAction) - @Composable private fun HomeContent( - featuredPodcasts: PersistentList, + featuredPodcasts: ImmutableList, selectedHomeCategory: HomeCategory, filterableCategoriesModel: FilterableCategoriesModel, podcastCategoryFilterResult: PodcastCategoryFilterResult, @@ -572,7 +574,7 @@ private fun HomeContent( @Composable private fun HomeContentGrid( - featuredPodcasts: PersistentList, + featuredPodcasts: ImmutableList, selectedHomeCategory: HomeCategory, filterableCategoriesModel: FilterableCategoriesModel, podcastCategoryFilterResult: PodcastCategoryFilterResult, @@ -630,7 +632,7 @@ private fun HomeContentGrid( @Composable private fun FollowedPodcastItem( - items: PersistentList, + items: ImmutableList, onPodcastUnfollowed: (PodcastInfo) -> Unit, navigateToPodcastDetails: (PodcastInfo) -> Unit, modifier: Modifier = Modifier, @@ -652,7 +654,7 @@ private fun FollowedPodcastItem( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun FollowedPodcasts( - items: PersistentList, + items: ImmutableList, onPodcastUnfollowed: (PodcastInfo) -> Unit, navigateToPodcastDetails: (PodcastInfo) -> Unit, modifier: Modifier = Modifier, @@ -767,7 +769,7 @@ private fun PreviewHome() { HomeScreen( windowSizeClass = CompactWindowSizeClass, isLoading = true, - featuredPodcasts = PreviewPodcasts.toPersistentList(), + featuredPodcasts = PreviewPodcasts.toImmutableList(), homeCategories = HomeCategory.entries, selectedHomeCategory = HomeCategory.Discover, filterableCategoriesModel = FilterableCategoriesModel( 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 b3106ee1ee..bab34a4af1 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 @@ -37,8 +37,8 @@ import com.example.jetcaster.core.model.asPodcastToEpisodeInfo import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.player.model.PlayerEpisode import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.ImmutableList import javax.inject.Inject -import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -229,7 +229,7 @@ sealed interface HomeAction { data class HomeScreenUiState( val isLoading: Boolean = true, val errorMessage: String? = null, - val featuredPodcasts: PersistentList = persistentListOf(), + val featuredPodcasts: ImmutableList = persistentListOf(), val selectedHomeCategory: HomeCategory = HomeCategory.Discover, val homeCategories: List = emptyList(), val filterableCategoriesModel: FilterableCategoriesModel = FilterableCategoriesModel(), diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt index 9d43327434..872b92e527 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt @@ -17,6 +17,7 @@ package com.example.jetcaster.ui.podcast import android.net.Uri +import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.repository.EpisodeStore @@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +@Immutable sealed interface PodcastUiState { data object Loading : PodcastUiState data class Ready(val podcast: PodcastInfo, val episodes: List) : PodcastUiState diff --git a/Jetcaster/stability_config.conf b/Jetcaster/stability_config.conf new file mode 100644 index 0000000000..a2593c14d3 --- /dev/null +++ b/Jetcaster/stability_config.conf @@ -0,0 +1,2 @@ +java.time.* +kotlin.collections.* diff --git a/Jetcaster/tv/build.gradle.kts b/Jetcaster/tv/build.gradle.kts index 9b890da4f1..ba509ef74d 100644 --- a/Jetcaster/tv/build.gradle.kts +++ b/Jetcaster/tv/build.gradle.kts @@ -92,6 +92,12 @@ android { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } + + composeCompiler { + reportsDestination = layout.buildDirectory.dir("compose_compiler") + metricsDestination = layout.buildDirectory.dir("compose_compiler") + stabilityConfigurationFiles = listOf(rootProject.layout.projectDirectory.file("stability_config.conf")) + } } dependencies { diff --git a/Jetcaster/wear/build.gradle b/Jetcaster/wear/build.gradle index a21e5a5812..fa50b16fce 100644 --- a/Jetcaster/wear/build.gradle +++ b/Jetcaster/wear/build.gradle @@ -70,6 +70,16 @@ android { excludes += "rome-utils-" + libs.rometools.rome.get().version + ".jar" } } + + composeCompiler { + + } + + composeCompiler { + reportsDestination = layout.buildDirectory.dir("compose_compiler") + metricsDestination = layout.buildDirectory.dir("compose_compiler") + stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf") + } } dependencies { From 7ea5a2101503bfd6178af59166a59b6380f9d8db Mon Sep 17 00:00:00 2001 From: hoangchungk53qx1 Date: Wed, 20 Aug 2025 19:38:19 +0700 Subject: [PATCH 2/4] Fix typo in toggleSubscribe method name in PodcastDetailsViewModel --- .../com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt | 2 +- .../com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt index 33dcc13ccb..c9890ab707 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt @@ -101,7 +101,7 @@ fun PodcastDetailsScreen( PodcastDetailsScreen( podcast = s.podcast, episodes = s.episodes, - toggleSubscribe = viewModel::toggleSusbcribe, + toggleSubscribe = viewModel::toggleSubscribe, onQueueEpisode = viewModel::onQueueEpisode, removeFromQueue = viewModel::deleteEpisode, navigateToPlayer = navigateToPlayer, diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt index 872b92e527..d77b68d804 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt @@ -73,7 +73,7 @@ class PodcastDetailsViewModel @AssistedInject constructor( initialValue = PodcastUiState.Loading, ) - fun toggleSusbcribe(podcast: PodcastInfo) { + fun toggleSubscribe(podcast: PodcastInfo) { viewModelScope.launch { podcastStore.togglePodcastFollowed(podcast.uri) } From c1a45f44866bc1b8b4ebbe6862a952fbe1f3a5d3 Mon Sep 17 00:00:00 2001 From: hoangchungk53qx1 Date: Thu, 21 Aug 2025 14:11:07 +0700 Subject: [PATCH 3/4] Refactor composeCompiler configuration in build.gradle --- Jetcaster/wear/build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Jetcaster/wear/build.gradle b/Jetcaster/wear/build.gradle index fa50b16fce..77580edf3f 100644 --- a/Jetcaster/wear/build.gradle +++ b/Jetcaster/wear/build.gradle @@ -71,10 +71,6 @@ android { } } - composeCompiler { - - } - composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") From 2462d3278ce32cbcce9587a649f72e3a76adead3 Mon Sep 17 00:00:00 2001 From: hoangchungk53qx1 Date: Fri, 22 Aug 2025 03:18:14 +0700 Subject: [PATCH 4/4] spotless format --- .../src/main/java/com/example/jetcaster/ui/home/Home.kt | 4 +--- .../main/java/com/example/jetcaster/ui/home/HomeViewModel.kt | 2 +- 2 files changed, 2 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 f49c2d850a..7b363bfce0 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 @@ -115,13 +115,11 @@ import com.example.jetcaster.util.fullWidthItem import com.example.jetcaster.util.isCompact import com.example.jetcaster.util.quantityStringResource import com.example.jetcaster.util.radialGradientScrim -import kotlinx.collections.immutable.ImmutableList import java.time.Duration import java.time.LocalDateTime import java.time.OffsetDateTime -import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3AdaptiveApi::class) 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 bab34a4af1..924b97792a 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 @@ -37,8 +37,8 @@ import com.example.jetcaster.core.model.asPodcastToEpisodeInfo import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.player.model.PlayerEpisode import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.ImmutableList import javax.inject.Inject +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.ExperimentalCoroutinesApi