From a106ddc11b77a75062da4c3d7ab253ade3c125c8 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Tue, 2 Apr 2024 11:13:35 -0700 Subject: [PATCH 1/4] Add hilt. --- Jetcaster/app/build.gradle.kts | 14 +- .../example/jetcaster/JetcasterApplication.kt | 16 +- .../com/example/jetcaster/ui/JetcasterApp.kt | 64 +------ .../com/example/jetcaster/ui/MainActivity.kt | 2 + .../com/example/jetcaster/ui/home/Home.kt | 26 +-- .../jetcaster/ui/home/HomeViewModel.kt | 20 +-- .../jetcaster/ui/player/PlayerScreen.kt | 5 +- .../jetcaster/ui/player/PlayerViewModel.kt | 38 +---- .../ui/podcast/PodcastDetailsViewModel.kt | 56 ++---- Jetcaster/build.gradle.kts | 2 + Jetcaster/core/build.gradle.kts | 10 ++ .../example/jetcaster/core/data/Dispatcher.kt | 12 ++ .../jetcaster/core/data/di/CoreDiModule.kt | 160 ++++++++++++++++++ .../example/jetcaster/core/data/di/Graph.kt | 27 +-- .../domain/FilterableCategoriesUseCase.kt | 3 +- .../GetLatestFollowedEpisodesUseCase.kt | 8 +- .../domain/PodcastCategoryFilterUseCase.kt | 3 +- .../core/data/network/PodcastFetcher.kt | 15 +- .../core/data/repository/CategoryStore.kt | 3 +- .../core/data/repository/PodcastStore.kt | 2 +- .../data/repository/PodcastsRepository.kt | 7 +- .../core/player/MockEpisodePlayer.kt | 4 +- 22 files changed, 276 insertions(+), 221 deletions(-) create mode 100644 Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt create mode 100644 Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt diff --git a/Jetcaster/app/build.gradle.kts b/Jetcaster/app/build.gradle.kts index 713bfc50f5..80b1348d07 100644 --- a/Jetcaster/app/build.gradle.kts +++ b/Jetcaster/app/build.gradle.kts @@ -18,6 +18,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) + alias(libs.plugins.hilt) } android { @@ -95,18 +96,21 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.palette) - implementation(libs.androidx.activity.compose) - - implementation(libs.androidx.constraintlayout.compose) + // Dependency injection + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + // Compose + implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.foundation) - implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3.adaptive) implementation(libs.androidx.compose.material3.adaptive.layout) implementation(libs.androidx.compose.material3.adaptive.navigation) implementation(libs.androidx.compose.material3.window) - implementation(libs.androidx.compose.material.iconsExtended) + implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.tooling.preview) debugImplementation(libs.androidx.compose.ui.tooling) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt index 1493a62e3f..45cf1e705b 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt @@ -20,20 +20,16 @@ import android.app.Application import coil.ImageLoader import coil.ImageLoaderFactory import com.example.jetcaster.core.data.di.Graph +import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject /** * Application which sets up our dependency [Graph] with a context. */ +@HiltAndroidApp class JetcasterApplication : Application(), ImageLoaderFactory { - override fun onCreate() { - super.onCreate() - Graph.provide(this) - } - override fun newImageLoader(): ImageLoader { - return ImageLoader.Builder(this) - // Disable `Cache-Control` header support as some podcast images disable disk caching. - .respectCacheHeaders(false) - .build() - } + @Inject lateinit var imageLoader: ImageLoader + + override fun newImageLoader(): ImageLoader = imageLoader } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt index ece26ca3e3..93f63a9fa6 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt @@ -16,32 +16,18 @@ package com.example.jetcaster.ui -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.core.EaseIn -import androidx.compose.animation.core.EaseOut -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.window.layout.DisplayFeature import com.example.jetcaster.R -import com.example.jetcaster.core.data.di.Graph.episodePlayer -import com.example.jetcaster.core.data.di.Graph.episodeStore -import com.example.jetcaster.core.data.di.Graph.podcastStore import com.example.jetcaster.ui.home.MainScreen import com.example.jetcaster.ui.player.PlayerScreen -import com.example.jetcaster.ui.player.PlayerViewModel -import com.example.jetcaster.ui.podcast.PodcastDetailsScreen -import com.example.jetcaster.ui.podcast.PodcastDetailsViewModel @Composable fun JetcasterApp( @@ -62,61 +48,13 @@ fun JetcasterApp( } ) } - composable(Screen.Player.route) { backStackEntry -> - val playerViewModel: PlayerViewModel = viewModel( - factory = PlayerViewModel.provideFactory( - owner = backStackEntry, - defaultArgs = backStackEntry.arguments - ) - ) + composable(Screen.Player.route) { PlayerScreen( windowSizeClass, displayFeatures, - playerViewModel, onBackPress = appState::navigateBack ) } - composable( - route = Screen.PodcastDetails.route, - enterTransition = { - fadeIn( - animationSpec = tween( - 300, easing = LinearEasing - ) - ) + slideIntoContainer( - animationSpec = tween(300, easing = EaseIn), - towards = AnimatedContentTransitionScope.SlideDirection.Start - ) - }, - exitTransition = { - fadeOut( - animationSpec = tween( - 300, easing = LinearEasing - ) - ) + slideOutOfContainer( - animationSpec = tween(300, easing = EaseOut), - towards = AnimatedContentTransitionScope.SlideDirection.End - ) - } - ) { backStackEntry -> - val podcastDetailsViewModel: PodcastDetailsViewModel = viewModel( - factory = PodcastDetailsViewModel.provideFactory( - episodeStore = episodeStore, - podcastStore = podcastStore, - episodePlayer = episodePlayer, - owner = backStackEntry, - defaultArgs = backStackEntry.arguments - ) - ) - PodcastDetailsScreen( - viewModel = podcastDetailsViewModel, - navigateToPlayer = { episodePlayer -> - appState.navigateToPlayer(episodePlayer.uri, backStackEntry) - }, - navigateBack = appState::navigateBack, - showBackButton = true, - ) - } } } else { OfflineDialog { appState.refreshOnline() } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt index 8218625f3b..5e41c34b1b 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt @@ -24,7 +24,9 @@ import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSiz import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import com.example.jetcaster.ui.theme.JetcasterTheme import com.google.accompanist.adaptive.calculateDisplayFeatures +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index 24463855bd..cd524bbeda 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -84,16 +84,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import androidx.core.os.bundleOf +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.example.jetcaster.R import com.example.jetcaster.core.data.model.CategoryInfo @@ -103,7 +101,6 @@ import com.example.jetcaster.core.data.model.LibraryInfo import com.example.jetcaster.core.data.model.PlayerEpisode import com.example.jetcaster.core.data.model.PodcastCategoryFilterResult import com.example.jetcaster.core.data.model.PodcastInfo -import com.example.jetcaster.ui.Screen import com.example.jetcaster.ui.home.discover.discoverItems import com.example.jetcaster.ui.home.library.libraryItems import com.example.jetcaster.ui.podcast.PodcastDetailsScreen @@ -113,12 +110,12 @@ import com.example.jetcaster.util.ToggleFollowPodcastIconButton import com.example.jetcaster.util.fullWidthItem import com.example.jetcaster.util.isCompact import com.example.jetcaster.util.quantityStringResource -import java.time.Duration -import java.time.LocalDateTime -import java.time.OffsetDateTime import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch +import java.time.Duration +import java.time.LocalDateTime +import java.time.OffsetDateTime data class HomeState( val windowSizeClass: WindowSizeClass, @@ -150,7 +147,7 @@ private fun ThreePaneScaffoldNavigator.isMainPaneHidden(): Boolean { fun MainScreen( windowSizeClass: WindowSizeClass, navigateToPlayer: (EpisodeInfo) -> Unit, - viewModel: HomeViewModel = viewModel() + viewModel: HomeViewModel = hiltViewModel() ) { val viewState by viewModel.state.collectAsStateWithLifecycle() val navigator = rememberSupportingPaneScaffoldNavigator() @@ -193,15 +190,10 @@ fun MainScreen( value = navigator.scaffoldValue, directive = navigator.scaffoldDirective, supportingPane = { - val podcastDetailsViewModel: PodcastDetailsViewModel = viewModel( - key = podcastUri, - factory = PodcastDetailsViewModel.provideFactory( - owner = LocalSavedStateRegistryOwner.current, - defaultArgs = bundleOf( - Screen.ARG_PODCAST_URI to podcastUri - ) - ) - ) + val podcastDetailsViewModel = + hiltViewModel { + it.create(podcastUri) + } PodcastDetailsScreen( viewModel = podcastDetailsViewModel, navigateToPlayer = navigateToPlayer, diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt index 1a2b16aeff..cca4ffe8f6 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt @@ -19,7 +19,6 @@ package com.example.jetcaster.ui.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.database.model.EpisodeToPodcast -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.domain.FilterableCategoriesUseCase import com.example.jetcaster.core.data.domain.PodcastCategoryFilterUseCase import com.example.jetcaster.core.data.model.CategoryInfo @@ -34,6 +33,7 @@ import com.example.jetcaster.core.data.repository.PodcastStore import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.util.combine +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -43,17 +43,17 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch +import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) -class HomeViewModel( - private val podcastsRepository: PodcastsRepository = Graph.podcastRepository, - private val podcastStore: PodcastStore = Graph.podcastStore, - private val episodeStore: EpisodeStore = Graph.episodeStore, - private val podcastCategoryFilterUseCase: PodcastCategoryFilterUseCase = - Graph.podcastCategoryFilterUseCase, - private val filterableCategoriesUseCase: FilterableCategoriesUseCase = - Graph.filterableCategoriesUseCase, - private val episodePlayer: EpisodePlayer = Graph.episodePlayer +@HiltViewModel +class HomeViewModel @Inject constructor( + private val podcastsRepository: PodcastsRepository, + private val podcastStore: PodcastStore, + private val episodeStore: EpisodeStore, + private val podcastCategoryFilterUseCase: PodcastCategoryFilterUseCase, + private val filterableCategoriesUseCase: FilterableCategoriesUseCase, + private val episodePlayer: EpisodePlayer, ) : ViewModel() { // Holds our currently selected podcast in the library private val selectedLibraryPodcast = MutableStateFlow(null) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt index 6da07c69ac..93c7b8e0c7 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt @@ -81,6 +81,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.window.layout.DisplayFeature import androidx.window.layout.FoldingFeature import coil.compose.AsyncImage @@ -105,8 +106,8 @@ import java.time.Duration fun PlayerScreen( windowSizeClass: WindowSizeClass, displayFeatures: List, - viewModel: PlayerViewModel, - onBackPress: () -> Unit + onBackPress: () -> Unit, + viewModel: PlayerViewModel = hiltViewModel(), ) { val uiState = viewModel.uiState PlayerScreen( diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt index 64ec980f68..b1e15992bc 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt @@ -17,26 +17,24 @@ package com.example.jetcaster.ui.player import android.net.Uri -import android.os.Bundle import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.AbstractSavedStateViewModelFactory import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.savedstate.SavedStateRegistryOwner -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.model.toPlayerEpisode import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.player.EpisodePlayerState import com.example.jetcaster.ui.Screen -import java.time.Duration +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import java.time.Duration +import javax.inject.Inject data class PlayerUiState( val episodePlayerState: EpisodePlayerState = EpisodePlayerState() @@ -46,9 +44,10 @@ data class PlayerUiState( * ViewModel that handles the business logic and screen state of the Player screen */ @OptIn(ExperimentalCoroutinesApi::class) -class PlayerViewModel( - episodeStore: EpisodeStore = Graph.episodeStore, - private val episodePlayer: EpisodePlayer = Graph.episodePlayer, +@HiltViewModel +class PlayerViewModel @Inject constructor( + episodeStore: EpisodeStore, + private val episodePlayer: EpisodePlayer, savedStateHandle: SavedStateHandle ) : ViewModel() { @@ -100,27 +99,4 @@ class PlayerViewModel( fun onRewindBy(duration: Duration) { episodePlayer.rewindBy(duration) } - - /** - * Factory for PlayerViewModel that takes EpisodeStore, PodcastStore and EpisodePlayer as a - * dependency - */ - companion object { - fun provideFactory( - episodeStore: EpisodeStore = Graph.episodeStore, - episodePlayer: EpisodePlayer = Graph.episodePlayer, - owner: SavedStateRegistryOwner, - defaultArgs: Bundle? = null, - ): AbstractSavedStateViewModelFactory = - object : AbstractSavedStateViewModelFactory(owner, defaultArgs) { - @Suppress("UNCHECKED_CAST") - override fun create( - key: String, - modelClass: Class, - handle: SavedStateHandle - ): T { - return PlayerViewModel(episodeStore, episodePlayer, handle) as T - } - } - } } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt index d654d5ee51..22c78fa9c9 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsViewModel.kt @@ -17,13 +17,8 @@ package com.example.jetcaster.ui.podcast import android.net.Uri -import android.os.Bundle -import androidx.lifecycle.AbstractSavedStateViewModelFactory -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.savedstate.SavedStateRegistryOwner -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.model.EpisodeInfo import com.example.jetcaster.core.data.model.PlayerEpisode import com.example.jetcaster.core.data.model.PodcastInfo @@ -31,7 +26,10 @@ import com.example.jetcaster.core.data.model.asExternalModel import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastStore import com.example.jetcaster.core.player.EpisodePlayer -import com.example.jetcaster.ui.Screen +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -49,19 +47,20 @@ sealed interface PodcastUiState { /** * ViewModel that handles the business logic and screen state of the Podcast details screen. */ -class PodcastDetailsViewModel( - private val episodeStore: EpisodeStore = Graph.episodeStore, - private val episodePlayer: EpisodePlayer = Graph.episodePlayer, - private val podcastStore: PodcastStore = Graph.podcastStore, - savedStateHandle: SavedStateHandle +@HiltViewModel(assistedFactory = PodcastDetailsViewModel.Factory::class) +class PodcastDetailsViewModel @AssistedInject constructor( + private val episodeStore: EpisodeStore, + private val episodePlayer: EpisodePlayer, + private val podcastStore: PodcastStore, + @Assisted private val podcastUri: String, ) : ViewModel() { - private val podcastUri = Uri.decode(savedStateHandle.get(Screen.ARG_PODCAST_URI)!!) + private val decodedPodcastUri = Uri.decode(podcastUri) val state: StateFlow = combine( - podcastStore.podcastWithExtraInfo(podcastUri), - episodeStore.episodesInPodcast(podcastUri) + podcastStore.podcastWithExtraInfo(decodedPodcastUri), + episodeStore.episodesInPodcast(decodedPodcastUri) ) { podcast, episodeToPodcasts -> val episodes = episodeToPodcasts.map { it.episode.asExternalModel() } PodcastUiState.Ready( @@ -84,31 +83,8 @@ class PodcastDetailsViewModel( episodePlayer.addToQueue(playerEpisode) } - /** - * Factory for [PodcastDetailsViewModel]. - */ - companion object { - fun provideFactory( - episodeStore: EpisodeStore = Graph.episodeStore, - podcastStore: PodcastStore = Graph.podcastStore, - episodePlayer: EpisodePlayer = Graph.episodePlayer, - owner: SavedStateRegistryOwner, - defaultArgs: Bundle? = null, - ): AbstractSavedStateViewModelFactory = - object : AbstractSavedStateViewModelFactory(owner, defaultArgs) { - @Suppress("UNCHECKED_CAST") - override fun create( - key: String, - modelClass: Class, - handle: SavedStateHandle - ): T { - return PodcastDetailsViewModel( - episodeStore = episodeStore, - episodePlayer = episodePlayer, - podcastStore = podcastStore, - savedStateHandle = handle - ) as T - } - } + @AssistedFactory + interface Factory { + fun create(podcastUri: String): PodcastDetailsViewModel } } diff --git a/Jetcaster/build.gradle.kts b/Jetcaster/build.gradle.kts index c9c401efd5..d3fc5aca1b 100644 --- a/Jetcaster/build.gradle.kts +++ b/Jetcaster/build.gradle.kts @@ -20,6 +20,8 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.gradle.versions) alias(libs.plugins.version.catalog.update) + alias(libs.plugins.ksp) apply false + alias(libs.plugins.hilt) apply false } apply("${project.rootDir}/buildscripts/toml-updater-config.gradle") diff --git a/Jetcaster/core/build.gradle.kts b/Jetcaster/core/build.gradle.kts index b82ab25e3a..2457fad326 100644 --- a/Jetcaster/core/build.gradle.kts +++ b/Jetcaster/core/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) + alias(libs.plugins.hilt) } // TODO(chris): Set up convention plugin @@ -38,14 +39,23 @@ dependencies { implementation(libs.androidx.appcompat) implementation(libs.androidx.compose.runtime) + // Image loading implementation(libs.coil.kt.compose) + // Compose val composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) + // Dependency injection + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + + // Networking implementation(libs.okhttp3) implementation(libs.okhttp.logging) + // Database implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) ksp(libs.androidx.room.compiler) diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt new file mode 100644 index 0000000000..c5f5f348ae --- /dev/null +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt @@ -0,0 +1,12 @@ +package com.example.jetcaster.core.data + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class Dispatcher(val jetcasterDispatcher: JetcasterDispatchers) + +enum class JetcasterDispatchers { + Main, + IO, +} diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt new file mode 100644 index 0000000000..93c3d1cd43 --- /dev/null +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt @@ -0,0 +1,160 @@ +package com.example.jetcaster.core.data.di + +import android.content.Context +import androidx.room.Room +import coil.ImageLoader +import com.example.jetcaster.core.BuildConfig +import com.example.jetcaster.core.data.Dispatcher +import com.example.jetcaster.core.data.JetcasterDispatchers +import com.example.jetcaster.core.data.database.JetcasterDatabase +import com.example.jetcaster.core.data.database.dao.CategoriesDao +import com.example.jetcaster.core.data.database.dao.EpisodesDao +import com.example.jetcaster.core.data.database.dao.PodcastCategoryEntryDao +import com.example.jetcaster.core.data.database.dao.PodcastFollowedEntryDao +import com.example.jetcaster.core.data.database.dao.PodcastsDao +import com.example.jetcaster.core.data.database.dao.TransactionRunner +import com.example.jetcaster.core.data.repository.CategoryStore +import com.example.jetcaster.core.data.repository.EpisodeStore +import com.example.jetcaster.core.data.repository.LocalCategoryStore +import com.example.jetcaster.core.data.repository.LocalEpisodeStore +import com.example.jetcaster.core.data.repository.LocalPodcastStore +import com.example.jetcaster.core.data.repository.PodcastStore +import com.example.jetcaster.core.player.EpisodePlayer +import com.example.jetcaster.core.player.MockEpisodePlayer +import com.rometools.rome.io.SyndFeedInput +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import okhttp3.Cache +import okhttp3.OkHttpClient +import okhttp3.logging.LoggingEventListener +import java.io.File +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object CoreDiModule { + + @Provides + @Singleton + fun provideOkHttpClient( + @ApplicationContext context: Context + ): OkHttpClient = OkHttpClient.Builder() + .cache(Cache(File(context.cacheDir, "http_cache"), (20 * 1024 * 1024).toLong())) + .apply { + if (BuildConfig.DEBUG) eventListenerFactory(LoggingEventListener.Factory()) + } + .build() + + @Provides + @Singleton + fun provideDatabase( + @ApplicationContext context: Context + ): JetcasterDatabase = + Room.databaseBuilder(context, JetcasterDatabase::class.java, "data.db") + // This is not recommended for normal apps, but the goal of this sample isn't to + // showcase all of Room. + .fallbackToDestructiveMigration() + .build() + + @Provides + @Singleton + fun provideImageLoader( + @ApplicationContext context: Context + ): ImageLoader = ImageLoader.Builder(context) + // Disable `Cache-Control` header support as some podcast images disable disk caching. + .respectCacheHeaders(false) + .build() + + @Provides + @Singleton + fun provideCategoriesDao( + database: JetcasterDatabase + ): CategoriesDao = database.categoriesDao() + + @Provides + @Singleton + fun providePodcastCategoryEntryDao( + database: JetcasterDatabase + ): PodcastCategoryEntryDao = database.podcastCategoryEntryDao() + + @Provides + @Singleton + fun providePodcastsDao( + database: JetcasterDatabase + ): PodcastsDao = database.podcastsDao() + + @Provides + @Singleton + fun provideEpisodesDao( + database: JetcasterDatabase + ): EpisodesDao = database.episodesDao() + + @Provides + @Singleton + fun providePodcastFollowedEntryDao( + database: JetcasterDatabase + ): PodcastFollowedEntryDao = database.podcastFollowedEntryDao() + + @Provides + @Singleton + fun provideTransactionRunner( + database: JetcasterDatabase + ): TransactionRunner = database.transactionRunnerDao() + + @Provides + @Singleton + fun provideSyndFeedInput() = SyndFeedInput() + + @Provides + @Dispatcher(JetcasterDispatchers.IO) + @Singleton + fun provideIODispatcher(): CoroutineDispatcher = Dispatchers.IO + + @Provides + @Dispatcher(JetcasterDispatchers.Main) + @Singleton + fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main + + @Provides + @Singleton + fun provideEpisodeStore( + episodeDao: EpisodesDao + ): EpisodeStore = LocalEpisodeStore(episodeDao) + + @Provides + @Singleton + fun providePodcastStore( + podcastDao: PodcastsDao, + podcastFollowedEntryDao: PodcastFollowedEntryDao, + transactionRunner: TransactionRunner, + ): PodcastStore = LocalPodcastStore( + podcastDao = podcastDao, + podcastFollowedEntryDao = podcastFollowedEntryDao, + transactionRunner = transactionRunner + ) + + @Provides + @Singleton + fun provideCategoryStore( + categoriesDao: CategoriesDao, + podcastCategoryEntryDao: PodcastCategoryEntryDao, + podcastDao: PodcastsDao, + episodeDao: EpisodesDao, + ): CategoryStore = LocalCategoryStore( + episodesDao = episodeDao, + podcastsDao = podcastDao, + categoriesDao = categoriesDao, + categoryEntryDao = podcastCategoryEntryDao, + ) + + @Provides + @Singleton + fun provideEpisodePlayer( + @Dispatcher(JetcasterDispatchers.Main) mainDispatcher: CoroutineDispatcher + ): EpisodePlayer = MockEpisodePlayer(mainDispatcher) +} diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt index c53f2f024d..039512676e 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt @@ -17,12 +17,9 @@ package com.example.jetcaster.core.data.di import android.content.Context -import androidx.room.Room -import com.example.jetcaster.core.BuildConfig import com.example.jetcaster.core.data.database.JetcasterDatabase import com.example.jetcaster.core.data.database.dao.TransactionRunner import com.example.jetcaster.core.data.domain.FilterableCategoriesUseCase -import com.example.jetcaster.core.data.domain.GetLatestFollowedEpisodesUseCase import com.example.jetcaster.core.data.domain.PodcastCategoryFilterUseCase import com.example.jetcaster.core.data.network.PodcastsFetcher import com.example.jetcaster.core.data.repository.CategoryStore @@ -33,12 +30,9 @@ import com.example.jetcaster.core.data.repository.LocalPodcastStore import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.core.player.MockEpisodePlayer import com.rometools.rome.io.SyndFeedInput -import java.io.File import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers -import okhttp3.Cache import okhttp3.OkHttpClient -import okhttp3.logging.LoggingEventListener /** * A very simple global singleton dependency graph. @@ -93,13 +87,6 @@ object Graph { ) } - val getLatestFollowedEpisodesUseCase by lazy { - GetLatestFollowedEpisodesUseCase( - episodeStore = episodeStore, - podcastStore = podcastStore - ) - } - val podcastCategoryFilterUseCase by lazy { PodcastCategoryFilterUseCase( categoryStore = categoryStore @@ -128,17 +115,7 @@ object Graph { get() = Dispatchers.IO fun provide(context: Context) { - okHttpClient = OkHttpClient.Builder() - .cache(Cache(File(context.cacheDir, "http_cache"), (20 * 1024 * 1024).toLong())) - .apply { - if (BuildConfig.DEBUG) eventListenerFactory(LoggingEventListener.Factory()) - } - .build() - - database = Room.databaseBuilder(context, JetcasterDatabase::class.java, "data.db") - // This is not recommended for normal apps, but the goal of this sample isn't to - // showcase all of Room. - .fallbackToDestructiveMigration() - .build() + okHttpClient = CoreDiModule.provideOkHttpClient(context) + database = CoreDiModule.provideDatabase(context) } } diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt index eedf1f708b..97c0738dc4 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt @@ -22,11 +22,12 @@ import com.example.jetcaster.core.data.model.asExternalModel import com.example.jetcaster.core.data.repository.CategoryStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import javax.inject.Inject /** * Use case for categories that can be used to filter podcasts. */ -class FilterableCategoriesUseCase( +class FilterableCategoriesUseCase @Inject constructor( private val categoryStore: CategoryStore ) { /** diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt index 98f273b4fa..08d007c052 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt @@ -17,19 +17,19 @@ package com.example.jetcaster.core.data.domain import com.example.jetcaster.core.data.database.model.EpisodeToPodcast -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastStore import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest +import javax.inject.Inject /** * A use case which returns all the latest episodes from all the podcasts the user follows. */ -class GetLatestFollowedEpisodesUseCase( - private val episodeStore: EpisodeStore = Graph.episodeStore, - private val podcastStore: PodcastStore = Graph.podcastStore, +class GetLatestFollowedEpisodesUseCase @Inject constructor( + private val episodeStore: EpisodeStore, + private val podcastStore: PodcastStore, ) { @OptIn(ExperimentalCoroutinesApi::class) operator fun invoke(): Flow> = diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt index de978cc18d..2bec115659 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt @@ -25,11 +25,12 @@ import com.example.jetcaster.core.data.repository.CategoryStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject /** * A use case which returns top podcasts and matching episodes in a given [Category]. */ -class PodcastCategoryFilterUseCase( +class PodcastCategoryFilterUseCase @Inject constructor( private val categoryStore: CategoryStore ) { operator fun invoke(category: CategoryInfo?): Flow { diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt index eaf619487d..7cb5045f58 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt @@ -17,6 +17,8 @@ package com.example.jetcaster.core.data.network import coil.network.HttpException +import com.example.jetcaster.core.data.Dispatcher +import com.example.jetcaster.core.data.JetcasterDispatchers import com.example.jetcaster.core.data.database.model.Category import com.example.jetcaster.core.data.database.model.Episode import com.example.jetcaster.core.data.database.model.Podcast @@ -25,10 +27,6 @@ import com.rometools.modules.itunes.FeedInformation import com.rometools.rome.feed.synd.SyndEntry import com.rometools.rome.feed.synd.SyndFeed import com.rometools.rome.io.SyndFeedInput -import java.time.Duration -import java.time.Instant -import java.time.ZoneOffset -import java.util.concurrent.TimeUnit import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow @@ -39,6 +37,11 @@ import kotlinx.coroutines.withContext import okhttp3.CacheControl import okhttp3.OkHttpClient import okhttp3.Request +import java.time.Duration +import java.time.Instant +import java.time.ZoneOffset +import java.util.concurrent.TimeUnit +import javax.inject.Inject /** * A class which fetches some selected podcast RSS feeds. @@ -47,10 +50,10 @@ import okhttp3.Request * @param syndFeedInput [SyndFeedInput] to use for parsing RSS feeds. * @param ioDispatcher [CoroutineDispatcher] to use for running fetch requests. */ -class PodcastsFetcher( +class PodcastsFetcher @Inject constructor( private val okHttpClient: OkHttpClient, private val syndFeedInput: SyndFeedInput, - private val ioDispatcher: CoroutineDispatcher + @Dispatcher(JetcasterDispatchers.IO) private val ioDispatcher: CoroutineDispatcher ) { /** diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt index 8d067157d0..9e926520d5 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt @@ -25,6 +25,7 @@ import com.example.jetcaster.core.data.database.model.EpisodeToPodcast import com.example.jetcaster.core.data.database.model.PodcastCategoryEntry import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo import kotlinx.coroutines.flow.Flow +import javax.inject.Inject interface CategoryStore { /** @@ -66,7 +67,7 @@ interface CategoryStore { /** * A data repository for [Category] instances. */ -class LocalCategoryStore( +class LocalCategoryStore constructor( private val categoriesDao: CategoriesDao, private val categoryEntryDao: PodcastCategoryEntryDao, private val episodesDao: EpisodesDao, diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastStore.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastStore.kt index 1241082aa7..ee809c9e30 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastStore.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastStore.kt @@ -91,7 +91,7 @@ interface PodcastStore { /** * A data repository for [Podcast] instances. */ -class LocalPodcastStore( +class LocalPodcastStore constructor( private val podcastDao: PodcastsDao, private val podcastFollowedEntryDao: PodcastFollowedEntryDao, private val transactionRunner: TransactionRunner diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt index 52c71cff78..1198650e60 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt @@ -16,6 +16,8 @@ package com.example.jetcaster.core.data.repository +import com.example.jetcaster.core.data.Dispatcher +import com.example.jetcaster.core.data.JetcasterDispatchers import com.example.jetcaster.core.data.database.dao.TransactionRunner import com.example.jetcaster.core.data.network.PodcastRssResponse import com.example.jetcaster.core.data.network.PodcastsFetcher @@ -26,17 +28,18 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import javax.inject.Inject /** * Data repository for Podcasts. */ -class PodcastsRepository( +class PodcastsRepository @Inject constructor( private val podcastsFetcher: PodcastsFetcher, private val podcastStore: PodcastStore, private val episodeStore: EpisodeStore, private val categoryStore: CategoryStore, private val transactionRunner: TransactionRunner, - mainDispatcher: CoroutineDispatcher + @Dispatcher(JetcasterDispatchers.Main) mainDispatcher: CoroutineDispatcher ) { private var refreshingJob: Job? = null diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt index 34662b24da..14dfcbd1bd 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt @@ -17,8 +17,6 @@ package com.example.jetcaster.core.player import com.example.jetcaster.core.data.model.PlayerEpisode -import java.time.Duration -import kotlin.reflect.KProperty import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -31,6 +29,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import java.time.Duration +import kotlin.reflect.KProperty class MockEpisodePlayer( private val mainDispatcher: CoroutineDispatcher From 7634e9ddb638931dab2bd90bac15c760123ee877 Mon Sep 17 00:00:00 2001 From: arriolac Date: Tue, 2 Apr 2024 22:19:34 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A4=96=20Apply=20Spotless?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/jetcaster/ui/home/Home.kt | 6 +++--- .../jetcaster/ui/home/HomeViewModel.kt | 2 +- .../jetcaster/ui/player/PlayerViewModel.kt | 4 ++-- .../example/jetcaster/core/data/Dispatcher.kt | 16 +++++++++++++++ .../jetcaster/core/data/di/CoreDiModule.kt | 20 +++++++++++++++++-- .../domain/FilterableCategoriesUseCase.kt | 2 +- .../GetLatestFollowedEpisodesUseCase.kt | 2 +- .../domain/PodcastCategoryFilterUseCase.kt | 2 +- .../core/data/network/PodcastFetcher.kt | 10 +++++----- .../core/data/repository/CategoryStore.kt | 1 - .../data/repository/PodcastsRepository.kt | 2 +- .../core/player/MockEpisodePlayer.kt | 4 ++-- 12 files changed, 51 insertions(+), 20 deletions(-) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index cd524bbeda..376e255c91 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -110,12 +110,12 @@ import com.example.jetcaster.util.ToggleFollowPodcastIconButton import com.example.jetcaster.util.fullWidthItem import com.example.jetcaster.util.isCompact import com.example.jetcaster.util.quantityStringResource -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.launch import java.time.Duration import java.time.LocalDateTime import java.time.OffsetDateTime +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.launch data class HomeState( val windowSizeClass: WindowSizeClass, diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt index cca4ffe8f6..bfeddd3e28 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt @@ -34,6 +34,7 @@ import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.util.combine import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -43,7 +44,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch -import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt index b1e15992bc..7748a10c20 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt @@ -29,12 +29,12 @@ import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.player.EpisodePlayerState import com.example.jetcaster.ui.Screen import dagger.hilt.android.lifecycle.HiltViewModel +import java.time.Duration +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import java.time.Duration -import javax.inject.Inject data class PlayerUiState( val episodePlayerState: EpisodePlayerState = EpisodePlayerState() diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt index c5f5f348ae..a57199979c 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/Dispatcher.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.jetcaster.core.data import javax.inject.Qualifier diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt index 93c3d1cd43..7878b9be97 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/CoreDiModule.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.jetcaster.core.data.di import android.content.Context @@ -27,13 +43,13 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import java.io.File +import javax.inject.Singleton import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.logging.LoggingEventListener -import java.io.File -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt index 97c0738dc4..c7255b9466 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/FilterableCategoriesUseCase.kt @@ -20,9 +20,9 @@ import com.example.jetcaster.core.data.model.CategoryInfo import com.example.jetcaster.core.data.model.FilterableCategoriesModel import com.example.jetcaster.core.data.model.asExternalModel import com.example.jetcaster.core.data.repository.CategoryStore +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import javax.inject.Inject /** * Use case for categories that can be used to filter podcasts. diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt index 08d007c052..8d87799302 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/GetLatestFollowedEpisodesUseCase.kt @@ -19,10 +19,10 @@ package com.example.jetcaster.core.data.domain import com.example.jetcaster.core.data.database.model.EpisodeToPodcast import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastStore +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest -import javax.inject.Inject /** * A use case which returns all the latest episodes from all the podcasts the user follows. diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt index 2bec115659..68aa0d22a6 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/domain/PodcastCategoryFilterUseCase.kt @@ -22,10 +22,10 @@ import com.example.jetcaster.core.data.model.PodcastCategoryFilterResult import com.example.jetcaster.core.data.model.asExternalModel import com.example.jetcaster.core.data.model.asPodcastCategoryEpisode import com.example.jetcaster.core.data.repository.CategoryStore +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject /** * A use case which returns top podcasts and matching episodes in a given [Category]. diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt index 7cb5045f58..34e7030c93 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/network/PodcastFetcher.kt @@ -27,6 +27,11 @@ import com.rometools.modules.itunes.FeedInformation import com.rometools.rome.feed.synd.SyndEntry import com.rometools.rome.feed.synd.SyndFeed import com.rometools.rome.io.SyndFeedInput +import java.time.Duration +import java.time.Instant +import java.time.ZoneOffset +import java.util.concurrent.TimeUnit +import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow @@ -37,11 +42,6 @@ import kotlinx.coroutines.withContext import okhttp3.CacheControl import okhttp3.OkHttpClient import okhttp3.Request -import java.time.Duration -import java.time.Instant -import java.time.ZoneOffset -import java.util.concurrent.TimeUnit -import javax.inject.Inject /** * A class which fetches some selected podcast RSS feeds. diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt index 9e926520d5..a69d082652 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/CategoryStore.kt @@ -25,7 +25,6 @@ import com.example.jetcaster.core.data.database.model.EpisodeToPodcast import com.example.jetcaster.core.data.database.model.PodcastCategoryEntry import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo import kotlinx.coroutines.flow.Flow -import javax.inject.Inject interface CategoryStore { /** diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt index 1198650e60..cb9b308405 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/repository/PodcastsRepository.kt @@ -22,13 +22,13 @@ import com.example.jetcaster.core.data.database.dao.TransactionRunner import com.example.jetcaster.core.data.network.PodcastRssResponse import com.example.jetcaster.core.data.network.PodcastsFetcher import com.example.jetcaster.core.data.network.SampleFeeds +import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import javax.inject.Inject /** * Data repository for Podcasts. diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt index 14dfcbd1bd..34662b24da 100644 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt +++ b/Jetcaster/core/src/main/java/com/example/jetcaster/core/player/MockEpisodePlayer.kt @@ -17,6 +17,8 @@ package com.example.jetcaster.core.player import com.example.jetcaster.core.data.model.PlayerEpisode +import java.time.Duration +import kotlin.reflect.KProperty import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -29,8 +31,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import java.time.Duration -import kotlin.reflect.KProperty class MockEpisodePlayer( private val mainDispatcher: CoroutineDispatcher From b136c750e55b50773f82050af9e86b13d3f28e7d Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Wed, 3 Apr 2024 10:37:59 -0700 Subject: [PATCH 3/4] Add Hilt to TV and Wear --- .../example/jetcaster/JetcasterApplication.kt | 1 - .../com/example/jetcaster/ui/home/Home.kt | 10 +- .../example/jetcaster/core/data/di/Graph.kt | 121 ------------------ Jetcaster/tv-app/build.gradle.kts | 8 +- .../example/jetcaster/tv/JetCasterTvApp.kt | 11 +- .../com/example/jetcaster/tv/MainActivity.kt | 2 + .../example/jetcaster/tv/ui/JetcasterApp.kt | 11 -- .../tv/ui/discover/DiscoverScreen.kt | 4 +- .../tv/ui/discover/DiscoverScreenViewModel.kt | 10 +- .../jetcaster/tv/ui/episode/EpisodeScreen.kt | 4 +- .../tv/ui/episode/EpisodeScreenViewModel.kt | 26 +--- .../jetcaster/tv/ui/library/LibraryScreen.kt | 4 +- .../tv/ui/library/LibraryScreenViewModel.kt | 12 +- .../jetcaster/tv/ui/podcast/PodcastScreen.kt | 3 +- .../tv/ui/podcast/PodcastScreenViewModel.kt | 26 +--- .../jetcaster/tv/ui/search/SearchScreen.kt | 4 +- .../tv/ui/search/SearchScreenViewModel.kt | 12 +- Jetcaster/wear/build.gradle | 10 +- .../jetcaster/JetcasterWearApplication.kt | 15 +-- .../com/example/jetcaster/MainActivity.kt | 14 +- .../example/jetcaster/ui/home/HomeScreen.kt | 5 +- .../jetcaster/ui/home/HomeViewModel.kt | 23 ++-- .../ui/library/LatestEpisodeViewModel.kt | 9 +- .../ui/library/LatestEpisodesScreen.kt | 3 +- .../jetcaster/ui/player/PlayerScreen.kt | 3 +- .../jetcaster/ui/player/PlayerViewModel.kt | 33 +---- 26 files changed, 104 insertions(+), 280 deletions(-) delete mode 100644 Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt index 45cf1e705b..120187d2d5 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/JetcasterApplication.kt @@ -19,7 +19,6 @@ package com.example.jetcaster import android.app.Application import coil.ImageLoader import coil.ImageLoaderFactory -import com.example.jetcaster.core.data.di.Graph import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index 376e255c91..f8cf9c3e24 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -110,12 +110,12 @@ import com.example.jetcaster.util.ToggleFollowPodcastIconButton import com.example.jetcaster.util.fullWidthItem import com.example.jetcaster.util.isCompact import com.example.jetcaster.util.quantityStringResource -import java.time.Duration -import java.time.LocalDateTime -import java.time.OffsetDateTime import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch +import java.time.Duration +import java.time.LocalDateTime +import java.time.OffsetDateTime data class HomeState( val windowSizeClass: WindowSizeClass, @@ -191,7 +191,9 @@ fun MainScreen( directive = navigator.scaffoldDirective, supportingPane = { val podcastDetailsViewModel = - hiltViewModel { + hiltViewModel( + key = podcastUri + ) { it.create(podcastUri) } PodcastDetailsScreen( diff --git a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt b/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt deleted file mode 100644 index 039512676e..0000000000 --- a/Jetcaster/core/src/main/java/com/example/jetcaster/core/data/di/Graph.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.jetcaster.core.data.di - -import android.content.Context -import com.example.jetcaster.core.data.database.JetcasterDatabase -import com.example.jetcaster.core.data.database.dao.TransactionRunner -import com.example.jetcaster.core.data.domain.FilterableCategoriesUseCase -import com.example.jetcaster.core.data.domain.PodcastCategoryFilterUseCase -import com.example.jetcaster.core.data.network.PodcastsFetcher -import com.example.jetcaster.core.data.repository.CategoryStore -import com.example.jetcaster.core.data.repository.EpisodeStore -import com.example.jetcaster.core.data.repository.LocalCategoryStore -import com.example.jetcaster.core.data.repository.LocalEpisodeStore -import com.example.jetcaster.core.data.repository.LocalPodcastStore -import com.example.jetcaster.core.data.repository.PodcastsRepository -import com.example.jetcaster.core.player.MockEpisodePlayer -import com.rometools.rome.io.SyndFeedInput -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import okhttp3.OkHttpClient - -/** - * A very simple global singleton dependency graph. - * - * For a real app, you would use something like Hilt/Dagger instead. - */ -object Graph { - lateinit var okHttpClient: OkHttpClient - - lateinit var database: JetcasterDatabase - private set - - private val transactionRunner: TransactionRunner - get() = database.transactionRunnerDao() - - private val syndFeedInput by lazy { SyndFeedInput() } - - val episodePlayer by lazy { - MockEpisodePlayer(mainDispatcher) - } - - val podcastRepository by lazy { - PodcastsRepository( - podcastsFetcher = podcastFetcher, - podcastStore = podcastStore, - episodeStore = episodeStore, - categoryStore = categoryStore, - transactionRunner = transactionRunner, - mainDispatcher = mainDispatcher - ) - } - - private val podcastFetcher by lazy { - PodcastsFetcher( - okHttpClient = okHttpClient, - syndFeedInput = syndFeedInput, - ioDispatcher = ioDispatcher - ) - } - - val podcastStore by lazy { - LocalPodcastStore( - podcastDao = database.podcastsDao(), - podcastFollowedEntryDao = database.podcastFollowedEntryDao(), - transactionRunner = transactionRunner - ) - } - - val episodeStore: EpisodeStore by lazy { - LocalEpisodeStore( - episodesDao = database.episodesDao() - ) - } - - val podcastCategoryFilterUseCase by lazy { - PodcastCategoryFilterUseCase( - categoryStore = categoryStore - ) - } - - val filterableCategoriesUseCase by lazy { - FilterableCategoriesUseCase( - categoryStore = categoryStore - ) - } - - val categoryStore: CategoryStore by lazy { - LocalCategoryStore( - categoriesDao = database.categoriesDao(), - categoryEntryDao = database.podcastCategoryEntryDao(), - episodesDao = database.episodesDao(), - podcastsDao = database.podcastsDao() - ) - } - - private val mainDispatcher: CoroutineDispatcher - get() = Dispatchers.Main - - private val ioDispatcher: CoroutineDispatcher - get() = Dispatchers.IO - - fun provide(context: Context) { - okHttpClient = CoreDiModule.provideOkHttpClient(context) - database = CoreDiModule.provideDatabase(context) - } -} diff --git a/Jetcaster/tv-app/build.gradle.kts b/Jetcaster/tv-app/build.gradle.kts index d748aaa616..36045066e3 100644 --- a/Jetcaster/tv-app/build.gradle.kts +++ b/Jetcaster/tv-app/build.gradle.kts @@ -18,6 +18,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) + alias(libs.plugins.hilt) } android { @@ -79,6 +80,11 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.coil.kt.compose) + // Dependency injection + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + implementation(project(":core")) implementation(project(":designsystem")) @@ -89,4 +95,4 @@ dependencies { debugImplementation(libs.androidx.compose.ui.test.manifest) coreLibraryDesugaring(libs.core.jdk.desugaring) -} \ No newline at end of file +} diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/JetCasterTvApp.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/JetCasterTvApp.kt index 413098fc13..0d85c0b841 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/JetCasterTvApp.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/JetCasterTvApp.kt @@ -17,12 +17,7 @@ package com.example.jetcaster.tv import android.app.Application -import com.example.jetcaster.core.data.di.Graph +import dagger.hilt.android.HiltAndroidApp -class JetCasterTvApp : Application() { - - override fun onCreate() { - super.onCreate() - Graph.provide(this) - } -} +@HiltAndroidApp +class JetCasterTvApp : Application() diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/MainActivity.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/MainActivity.kt index ec52101124..6052428d09 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/MainActivity.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/MainActivity.kt @@ -29,7 +29,9 @@ import androidx.tv.material3.Surface import androidx.tv.material3.Text import com.example.jetcaster.tv.ui.JetcasterApp import com.example.jetcaster.tv.ui.theme.JetcasterTheme +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : ComponentActivity() { @OptIn(ExperimentalTvMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/JetcasterApp.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/JetcasterApp.kt index 19025262e0..c9da96f1e8 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/JetcasterApp.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/JetcasterApp.kt @@ -29,7 +29,6 @@ import androidx.compose.material.icons.filled.VideoLibrary import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.tv.material3.ExperimentalTvMaterial3Api @@ -40,10 +39,8 @@ import androidx.tv.material3.NavigationDrawerItem import androidx.tv.material3.Text import com.example.jetcaster.tv.ui.discover.DiscoverScreen import com.example.jetcaster.tv.ui.episode.EpisodeScreen -import com.example.jetcaster.tv.ui.episode.EpisodeScreenViewModel import com.example.jetcaster.tv.ui.library.LibraryScreen import com.example.jetcaster.tv.ui.podcast.PodcastScreen -import com.example.jetcaster.tv.ui.podcast.PodcastScreenViewModel import com.example.jetcaster.tv.ui.profile.ProfileScreen import com.example.jetcaster.tv.ui.search.SearchScreen import com.example.jetcaster.tv.ui.settings.SettingsScreen @@ -165,11 +162,7 @@ private fun Route(jetcasterAppState: JetcasterAppState) { } composable(Screen.Podcast.route) { - val podcastScreenViewModel: PodcastScreenViewModel = viewModel( - factory = PodcastScreenViewModel.factory - ) PodcastScreen( - podcastScreenViewModel = podcastScreenViewModel, backToHomeScreen = jetcasterAppState::navigateToDiscover, playEpisode = {}, showEpisodeDetails = { jetcasterAppState.showEpisodeDetails(it.episode.uri) }, @@ -180,15 +173,11 @@ private fun Route(jetcasterAppState: JetcasterAppState) { } composable(Screen.Episode.route) { - val episodeScreenViewModel: EpisodeScreenViewModel = viewModel( - factory = EpisodeScreenViewModel.factory - ) EpisodeScreen( playEpisode = { jetcasterAppState.playEpisode(it.uri) }, backToHome = jetcasterAppState::navigateToDiscover, - episodeScreenViewModel = episodeScreenViewModel, ) } diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt index 2f5548b2e7..efcadd57b0 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreen.kt @@ -29,7 +29,7 @@ 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.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.navigation.compose.hiltViewModel import androidx.tv.foundation.lazy.list.TvLazyListState import androidx.tv.foundation.lazy.list.rememberTvLazyListState import androidx.tv.material3.ExperimentalTvMaterial3Api @@ -52,7 +52,7 @@ fun DiscoverScreen( showPodcastDetails: (Podcast) -> Unit, showEpisodeDetails: (EpisodeToPodcast) -> Unit, modifier: Modifier = Modifier, - discoverScreenViewModel: DiscoverScreenViewModel = viewModel() + discoverScreenViewModel: DiscoverScreenViewModel = hiltViewModel() ) { val uiState by discoverScreenViewModel.uiState.collectAsState() diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt index 3947036353..6448755a69 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt @@ -19,12 +19,12 @@ package com.example.jetcaster.tv.ui.discover import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.database.model.Category -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.repository.CategoryStore import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.tv.model.CategoryList import com.example.jetcaster.tv.model.EpisodeList import com.example.jetcaster.tv.model.PodcastList +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -34,10 +34,12 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class DiscoverScreenViewModel( - private val podcastsRepository: PodcastsRepository = Graph.podcastRepository, - private val categoryStore: CategoryStore = Graph.categoryStore, +@HiltViewModel +class DiscoverScreenViewModel @Inject constructor( + private val podcastsRepository: PodcastsRepository, + private val categoryStore: CategoryStore, ) : ViewModel() { private val _selectedCategory = MutableStateFlow(null) diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreen.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreen.kt index 76bdba6d50..a24780dda7 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreen.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreen.kt @@ -30,7 +30,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.navigation.compose.hiltViewModel import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text @@ -50,7 +50,7 @@ fun EpisodeScreen( playEpisode: (Episode) -> Unit, backToHome: () -> Unit, modifier: Modifier = Modifier, - episodeScreenViewModel: EpisodeScreenViewModel = viewModel() + episodeScreenViewModel: EpisodeScreenViewModel = hiltViewModel() ) { val uiState by episodeScreenViewModel.uiStateFlow.collectAsState() diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt index e3bacc7480..b3d879b652 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt @@ -16,26 +16,27 @@ package com.example.jetcaster.tv.ui.episode -import androidx.lifecycle.AbstractSavedStateViewModelFactory import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.database.model.Episode import com.example.jetcaster.core.data.database.model.EpisodeToPodcast -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.tv.ui.Screen +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class EpisodeScreenViewModel( +@HiltViewModel +class EpisodeScreenViewModel @Inject constructor( handle: SavedStateHandle, - podcastsRepository: PodcastsRepository = Graph.podcastRepository, - episodeStore: EpisodeStore = Graph.episodeStore, + podcastsRepository: PodcastsRepository, + episodeStore: EpisodeStore, ) : ViewModel() { private val episodeUri = handle.get(Screen.Episode.PARAMETER_NAME) @@ -70,21 +71,6 @@ class EpisodeScreenViewModel( podcastsRepository.updatePodcasts(false) } } - - companion object { - @Suppress("UNCHECKED_CAST") - val factory = object : AbstractSavedStateViewModelFactory() { - override fun create( - key: String, - modelClass: Class, - handle: SavedStateHandle - ): T { - return EpisodeScreenViewModel( - handle - ) as T - } - } - } } sealed interface EpisodeScreenUiState { diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreen.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreen.kt index bdbf1b5625..0b52136073 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreen.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreen.kt @@ -31,7 +31,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.navigation.compose.hiltViewModel import androidx.tv.material3.Button import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.MaterialTheme @@ -51,7 +51,7 @@ fun LibraryScreen( navigateToDiscover: () -> Unit, showPodcastDetails: (PodcastWithExtraInfo) -> Unit, showEpisodeDetails: (EpisodeToPodcast) -> Unit, - libraryScreenViewModel: LibraryScreenViewModel = viewModel() + libraryScreenViewModel: LibraryScreenViewModel = hiltViewModel() ) { val uiState by libraryScreenViewModel.uiState.collectAsState() when (val s = uiState) { diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt index 1855cbb192..79cbabef4b 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt @@ -18,12 +18,12 @@ package com.example.jetcaster.tv.ui.library import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastStore import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.tv.model.EpisodeList import com.example.jetcaster.tv.model.PodcastList +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -32,11 +32,13 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class LibraryScreenViewModel( - private val podcastsRepository: PodcastsRepository = Graph.podcastRepository, - private val episodeStore: EpisodeStore = Graph.episodeStore, - podcastStore: PodcastStore = Graph.podcastStore, +@HiltViewModel +class LibraryScreenViewModel @Inject constructor( + private val podcastsRepository: PodcastsRepository, + private val episodeStore: EpisodeStore, + podcastStore: PodcastStore, ) : ViewModel() { private val followingPodcastListFlow = podcastStore.followedPodcastsSortedByLastEpisode().map { diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreen.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreen.kt index 9c33abfb4e..c704d8f591 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreen.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.ButtonDefaults @@ -62,11 +63,11 @@ import com.example.jetcaster.tv.ui.theme.JetcasterAppDefaults @Composable fun PodcastScreen( - podcastScreenViewModel: PodcastScreenViewModel, backToHomeScreen: () -> Unit, playEpisode: (Episode) -> Unit, showEpisodeDetails: (EpisodeToPodcast) -> Unit, modifier: Modifier = Modifier, + podcastScreenViewModel: PodcastScreenViewModel = hiltViewModel(), ) { val uiState by podcastScreenViewModel.uiStateFlow.collectAsState() when (val s = uiState) { diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt index 8b39200abd..76f4a1ba19 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt @@ -16,16 +16,15 @@ package com.example.jetcaster.tv.ui.podcast -import androidx.lifecycle.AbstractSavedStateViewModelFactory import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.database.model.Podcast -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastStore import com.example.jetcaster.tv.model.EpisodeList import com.example.jetcaster.tv.ui.Screen +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -34,11 +33,13 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class PodcastScreenViewModel( +@HiltViewModel +class PodcastScreenViewModel @Inject constructor( handle: SavedStateHandle, - private val podcastStore: PodcastStore = Graph.podcastStore, - episodeStore: EpisodeStore = Graph.episodeStore, + private val podcastStore: PodcastStore, + episodeStore: EpisodeStore, ) : ViewModel() { private val podcastUri = handle.get(Screen.Podcast.PARAMETER_NAME) @@ -98,21 +99,6 @@ class PodcastScreenViewModel( } } } - - companion object { - @Suppress("UNCHECKED_CAST") - val factory = object : AbstractSavedStateViewModelFactory() { - override fun create( - key: String, - modelClass: Class, - handle: SavedStateHandle - ): T { - return PodcastScreenViewModel( - handle - ) as T - } - } - } } sealed interface PodcastScreenUiState { diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreen.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreen.kt index dd05155161..813cd19597 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreen.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreen.kt @@ -45,7 +45,7 @@ import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.navigation.compose.hiltViewModel import androidx.tv.foundation.lazy.grid.TvGridCells import androidx.tv.foundation.lazy.grid.TvGridItemSpan import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid @@ -68,7 +68,7 @@ import com.example.jetcaster.tv.ui.theme.JetcasterAppDefaults fun SearchScreen( onPodcastSelected: (PodcastWithExtraInfo) -> Unit, modifier: Modifier = Modifier, - searchScreenViewModel: SearchScreenViewModel = viewModel() + searchScreenViewModel: SearchScreenViewModel = hiltViewModel() ) { val uiState by searchScreenViewModel.uiStateFlow.collectAsState() diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt index 1f222d5b0f..254d3f1f04 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt @@ -19,7 +19,6 @@ package com.example.jetcaster.tv.ui.search import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.database.model.Category -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.repository.CategoryStore import com.example.jetcaster.core.data.repository.PodcastStore import com.example.jetcaster.core.data.repository.PodcastsRepository @@ -27,6 +26,7 @@ import com.example.jetcaster.tv.model.CategoryList import com.example.jetcaster.tv.model.CategorySelection import com.example.jetcaster.tv.model.CategorySelectionList import com.example.jetcaster.tv.model.PodcastList +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -35,11 +35,13 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import javax.inject.Inject -class SearchScreenViewModel( - private val podcastsRepository: PodcastsRepository = Graph.podcastRepository, - private val podcastStore: PodcastStore = Graph.podcastStore, - categoryStore: CategoryStore = Graph.categoryStore, +@HiltViewModel +class SearchScreenViewModel @Inject constructor( + private val podcastsRepository: PodcastsRepository, + private val podcastStore: PodcastStore, + categoryStore: CategoryStore, ) : ViewModel() { private val keywordFlow = MutableStateFlow("") diff --git a/Jetcaster/wear/build.gradle b/Jetcaster/wear/build.gradle index be948257b3..1cb72263a6 100644 --- a/Jetcaster/wear/build.gradle +++ b/Jetcaster/wear/build.gradle @@ -14,10 +14,11 @@ * limitations under the License. */ plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) alias libs.plugins.roborazzi alias(libs.plugins.ksp) + alias(libs.plugins.hilt) } android { @@ -105,6 +106,11 @@ dependencies { implementation libs.horologist.media.data implementation libs.horologist.images.coil + // Dependency injection + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + ksp(libs.hilt.compiler) + // Preview Tooling implementation libs.compose.ui.tooling.preview implementation(libs.androidx.compose.ui.tooling) diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/JetcasterWearApplication.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/JetcasterWearApplication.kt index 6c6f8fddc9..a633b967a8 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/JetcasterWearApplication.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/JetcasterWearApplication.kt @@ -20,13 +20,16 @@ import android.app.Application import android.os.StrictMode import coil.ImageLoader import coil.ImageLoaderFactory -import com.example.jetcaster.core.data.di.Graph +import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject +@HiltAndroidApp class JetcasterWearApplication : Application(), ImageLoaderFactory { + @Inject lateinit var imageLoader: ImageLoader + override fun onCreate() { super.onCreate() - Graph.provide(this) setStrictMode() } @@ -41,10 +44,6 @@ class JetcasterWearApplication : Application(), ImageLoaderFactory { ) } - override fun newImageLoader(): ImageLoader { - return ImageLoader.Builder(this) - // Disable `Cache-Control` header support as some podcast images disable disk caching. - .respectCacheHeaders(false) - .build() - } + override fun newImageLoader(): ImageLoader = + imageLoader } diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/MainActivity.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/MainActivity.kt index a94f2eec7b..33045b249d 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/MainActivity.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/MainActivity.kt @@ -24,7 +24,6 @@ import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.compose.ui.res.stringResource import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.viewmodel.compose.viewModel @@ -39,11 +38,8 @@ import com.example.jetcaster.ui.JetcasterNavController.navigateToUpNext import com.example.jetcaster.ui.JetcasterNavController.navigateToYourPodcast import com.example.jetcaster.ui.LatestEpisodes import com.example.jetcaster.ui.home.HomeScreen -import com.example.jetcaster.ui.home.HomeViewModel -import com.example.jetcaster.ui.library.LatestEpisodeViewModel import com.example.jetcaster.ui.library.LatestEpisodesScreen import com.example.jetcaster.ui.player.PlayerScreen -import com.example.jetcaster.ui.player.PlayerViewModel import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.audio.ui.VolumeViewModel import com.google.android.horologist.compose.layout.ScreenScaffold @@ -52,7 +48,9 @@ import com.google.android.horologist.media.ui.navigation.MediaNavController.navi import com.google.android.horologist.media.ui.navigation.MediaPlayerScaffold import com.google.android.horologist.media.ui.snackbar.SnackbarManager import com.google.android.horologist.media.ui.snackbar.SnackbarViewModel +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -79,12 +77,6 @@ fun WearApp() { playerScreen = { PlayerScreen( modifier = Modifier.fillMaxSize(), - - playerScreenViewModel = viewModel( - factory = PlayerViewModel.provideFactory( - owner = LocalSavedStateRegistryOwner.current - ) - ), volumeViewModel = volumeViewModel, onVolumeClick = { navController.navigateToVolume() @@ -93,7 +85,6 @@ fun WearApp() { }, libraryScreen = { HomeScreen( - homeViewModel = HomeViewModel(), onLatestEpisodeClick = { navController.navigateToLatestEpisode() }, onYourPodcastClick = { navController.navigateToYourPodcast() }, onUpNextClick = { navController.navigateToUpNext() }, @@ -123,7 +114,6 @@ fun WearApp() { LatestEpisodesScreen( columnState = columnState, playlistName = stringResource(id = R.string.latest_episodes), - latestEpisodeViewModel = LatestEpisodeViewModel(), onShuffleButtonClick = {}, onPlayButtonClick = {} ) diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeScreen.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeScreen.kt index fd0a1808ec..041c3355d7 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeScreen.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.stringResource +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.wear.compose.material.ChipDefaults import androidx.wear.compose.material.Text @@ -46,12 +47,12 @@ import com.google.android.horologist.images.coil.CoilPaintable @OptIn(ExperimentalHorologistApi::class) @Composable fun HomeScreen( - homeViewModel: HomeViewModel, onLatestEpisodeClick: () -> Unit, onYourPodcastClick: () -> Unit, onUpNextClick: () -> Unit, onErrorDialogCancelClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + homeViewModel: HomeViewModel = hiltViewModel(), ) { val columnState = rememberResponsiveColumnState( contentPadding = ScalingLazyColumnDefaults.padding( diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt index 9aebd9b3e9..ddc4b63c25 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt @@ -21,9 +21,7 @@ import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.database.model.EpisodeToPodcast import com.example.jetcaster.core.data.database.model.Podcast import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.domain.FilterableCategoriesUseCase -import com.example.jetcaster.core.data.domain.GetLatestFollowedEpisodesUseCase import com.example.jetcaster.core.data.domain.PodcastCategoryFilterUseCase import com.example.jetcaster.core.data.model.CategoryInfo import com.example.jetcaster.core.data.model.FilterableCategoriesModel @@ -34,6 +32,7 @@ import com.example.jetcaster.core.data.repository.PodcastStore import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.util.combine +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -43,19 +42,17 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch +import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) -class HomeViewModel( - private val podcastsRepository: PodcastsRepository = Graph.podcastRepository, - private val podcastStore: PodcastStore = Graph.podcastStore, - private val episodeStore: EpisodeStore = Graph.episodeStore, - private val getLatestFollowedEpisodesUseCase: GetLatestFollowedEpisodesUseCase = - Graph.getLatestFollowedEpisodesUseCase, - private val podcastCategoryFilterUseCase: PodcastCategoryFilterUseCase = - Graph.podcastCategoryFilterUseCase, - private val filterableCategoriesUseCase: FilterableCategoriesUseCase = - Graph.filterableCategoriesUseCase, - private val episodePlayer: EpisodePlayer = Graph.episodePlayer +@HiltViewModel +class HomeViewModel @Inject constructor( + private val podcastsRepository: PodcastsRepository, + private val podcastStore: PodcastStore, + private val episodeStore: EpisodeStore, + private val podcastCategoryFilterUseCase: PodcastCategoryFilterUseCase, + private val filterableCategoriesUseCase: FilterableCategoriesUseCase, + private val episodePlayer: EpisodePlayer, ) : ViewModel() { // Holds our currently selected podcast in the library private val selectedLibraryPodcast = MutableStateFlow(null) diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt index 8d193ae673..5837e848ed 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt @@ -19,17 +19,18 @@ package com.example.jetcaster.ui.library import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.database.model.EpisodeToPodcast -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.domain.GetLatestFollowedEpisodesUseCase import com.example.jetcaster.core.util.combine +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch +import javax.inject.Inject -class LatestEpisodeViewModel( - private val episodesFromFavouritePodcasts: GetLatestFollowedEpisodesUseCase = - Graph.getLatestFollowedEpisodesUseCase, +@HiltViewModel +class LatestEpisodeViewModel @Inject constructor( + private val episodesFromFavouritePodcasts: GetLatestFollowedEpisodesUseCase, ) : ViewModel() { // Holds our view state which the UI collects via [state] private val _state = MutableStateFlow(LatestEpisodeViewState()) diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodesScreen.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodesScreen.kt index 956268d291..ba4879b2e7 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodesScreen.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodesScreen.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.wear.compose.material.ChipDefaults import com.example.jetcaster.R @@ -52,10 +53,10 @@ import com.google.android.horologist.media.ui.screens.entity.EntityScreen public fun LatestEpisodesScreen( columnState: ScalingLazyColumnState, playlistName: String, - latestEpisodeViewModel: LatestEpisodeViewModel, onShuffleButtonClick: (EpisodeToPodcast) -> Unit, onPlayButtonClick: (EpisodeToPodcast) -> Unit, modifier: Modifier = Modifier, + latestEpisodeViewModel: LatestEpisodeViewModel = hiltViewModel() ) { val viewState by latestEpisodeViewModel.state.collectAsStateWithLifecycle() diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt index 9a99a1c919..92263c45bb 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt @@ -38,6 +38,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.platform.LocalView +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.wear.compose.foundation.ExperimentalWearFoundationApi import androidx.wear.compose.foundation.rememberActiveFocusRequester @@ -56,10 +57,10 @@ import com.google.android.horologist.media.ui.state.PlayerUiState @OptIn(ExperimentalHorologistApi::class, ExperimentalWearFoundationApi::class) @Composable fun PlayerScreen( - playerScreenViewModel: PlayerViewModel, volumeViewModel: VolumeViewModel, onVolumeClick: () -> Unit, modifier: Modifier = Modifier, + playerScreenViewModel: PlayerViewModel = hiltViewModel(), ) { val volumeUiState by volumeViewModel.volumeUiState.collectAsStateWithLifecycle() // val settingsState by playerScreenViewModel.settingsState.collectAsStateWithLifecycle() diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt index 667d938e58..27b0d5e1ee 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt @@ -17,21 +17,19 @@ package com.example.jetcaster.ui.player import android.net.Uri -import android.os.Bundle import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.AbstractSavedStateViewModelFactory import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.savedstate.SavedStateRegistryOwner -import com.example.jetcaster.core.data.di.Graph import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastStore -import java.time.Duration +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import java.time.Duration +import javax.inject.Inject data class PlayerUiState( val title: String = "", @@ -46,7 +44,8 @@ data class PlayerUiState( /** * ViewModel that handles the business logic and screen state of the Player screen */ -class PlayerViewModel( +@HiltViewModel +class PlayerViewModel @Inject constructor( episodeStore: EpisodeStore, podcastStore: PodcastStore, savedStateHandle: SavedStateHandle @@ -79,26 +78,4 @@ class PlayerViewModel( } } } - - /** - * Factory for PlayerViewModel that takes EpisodeStore and PodcastStore as a dependency - */ - companion object { - fun provideFactory( - episodeStore: EpisodeStore = Graph.episodeStore, - podcastStore: PodcastStore = Graph.podcastStore, - owner: SavedStateRegistryOwner, - defaultArgs: Bundle? = null, - ): AbstractSavedStateViewModelFactory = - object : AbstractSavedStateViewModelFactory(owner, defaultArgs) { - @Suppress("UNCHECKED_CAST") - override fun create( - key: String, - modelClass: Class, - handle: SavedStateHandle - ): T { - return PlayerViewModel(episodeStore, podcastStore, handle) as T - } - } - } } From f6c1e6ba89a40aeb282b2f9c6698e0a25c5e2e24 Mon Sep 17 00:00:00 2001 From: arriolac Date: Wed, 3 Apr 2024 18:34:27 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=A4=96=20Apply=20Spotless?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/main/java/com/example/jetcaster/ui/home/Home.kt | 6 +++--- .../jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt | 2 +- .../jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt | 2 +- .../jetcaster/tv/ui/library/LibraryScreenViewModel.kt | 2 +- .../jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt | 2 +- .../example/jetcaster/tv/ui/search/SearchScreenViewModel.kt | 2 +- .../java/com/example/jetcaster/ui/home/HomeViewModel.kt | 2 +- .../example/jetcaster/ui/library/LatestEpisodeViewModel.kt | 2 +- .../java/com/example/jetcaster/ui/player/PlayerViewModel.kt | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index f8cf9c3e24..df4f32ce67 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -110,12 +110,12 @@ import com.example.jetcaster.util.ToggleFollowPodcastIconButton import com.example.jetcaster.util.fullWidthItem import com.example.jetcaster.util.isCompact import com.example.jetcaster.util.quantityStringResource -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.launch import java.time.Duration import java.time.LocalDateTime import java.time.OffsetDateTime +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.launch data class HomeState( val windowSizeClass: WindowSizeClass, diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt index 6448755a69..19fca0bb9a 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/discover/DiscoverScreenViewModel.kt @@ -25,6 +25,7 @@ import com.example.jetcaster.tv.model.CategoryList import com.example.jetcaster.tv.model.EpisodeList import com.example.jetcaster.tv.model.PodcastList import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -34,7 +35,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import javax.inject.Inject @HiltViewModel class DiscoverScreenViewModel @Inject constructor( diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt index b3d879b652..8518074e29 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/episode/EpisodeScreenViewModel.kt @@ -25,12 +25,12 @@ import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.tv.ui.Screen import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import javax.inject.Inject @HiltViewModel class EpisodeScreenViewModel @Inject constructor( diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt index 79cbabef4b..92a968a201 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/library/LibraryScreenViewModel.kt @@ -24,6 +24,7 @@ import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.tv.model.EpisodeList import com.example.jetcaster.tv.model.PodcastList import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -32,7 +33,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import javax.inject.Inject @HiltViewModel class LibraryScreenViewModel @Inject constructor( diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt index 76f4a1ba19..c791355d9a 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/podcast/PodcastScreenViewModel.kt @@ -25,6 +25,7 @@ import com.example.jetcaster.core.data.repository.PodcastStore import com.example.jetcaster.tv.model.EpisodeList import com.example.jetcaster.tv.ui.Screen import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -33,7 +34,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import javax.inject.Inject @HiltViewModel class PodcastScreenViewModel @Inject constructor( diff --git a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt index 254d3f1f04..24863951ae 100644 --- a/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt +++ b/Jetcaster/tv-app/src/main/java/com/example/jetcaster/tv/ui/search/SearchScreenViewModel.kt @@ -27,6 +27,7 @@ import com.example.jetcaster.tv.model.CategorySelection import com.example.jetcaster.tv.model.CategorySelectionList import com.example.jetcaster.tv.model.PodcastList import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -35,7 +36,6 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import javax.inject.Inject @HiltViewModel class SearchScreenViewModel @Inject constructor( diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt index ddc4b63c25..5bed543e20 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/home/HomeViewModel.kt @@ -33,6 +33,7 @@ import com.example.jetcaster.core.data.repository.PodcastsRepository import com.example.jetcaster.core.player.EpisodePlayer import com.example.jetcaster.core.util.combine import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -42,7 +43,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch -import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt index 5837e848ed..9a13538d06 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/library/LatestEpisodeViewModel.kt @@ -22,11 +22,11 @@ import com.example.jetcaster.core.data.database.model.EpisodeToPodcast import com.example.jetcaster.core.data.domain.GetLatestFollowedEpisodesUseCase import com.example.jetcaster.core.util.combine import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch -import javax.inject.Inject @HiltViewModel class LatestEpisodeViewModel @Inject constructor( diff --git a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt index 27b0d5e1ee..723b4a105e 100644 --- a/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt +++ b/Jetcaster/wear/src/main/java/com/example/jetcaster/ui/player/PlayerViewModel.kt @@ -26,10 +26,10 @@ import androidx.lifecycle.viewModelScope import com.example.jetcaster.core.data.repository.EpisodeStore import com.example.jetcaster.core.data.repository.PodcastStore import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import java.time.Duration import javax.inject.Inject +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch data class PlayerUiState( val title: String = "",