Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions Jetcaster/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
}

android {
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,16 @@ 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

/**
* 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?) {
Expand Down
22 changes: 8 additions & 14 deletions Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -150,7 +147,7 @@ private fun <T> ThreePaneScaffoldNavigator<T>.isMainPaneHidden(): Boolean {
fun MainScreen(
windowSizeClass: WindowSizeClass,
navigateToPlayer: (EpisodeInfo) -> Unit,
viewModel: HomeViewModel = viewModel()
viewModel: HomeViewModel = hiltViewModel()
) {
val viewState by viewModel.state.collectAsStateWithLifecycle()
val navigator = rememberSupportingPaneScaffoldNavigator<String>()
Expand Down Expand Up @@ -193,15 +190,12 @@ 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<PodcastDetailsViewModel, PodcastDetailsViewModel.Factory>(
key = podcastUri
) {
it.create(podcastUri)
}
PodcastDetailsScreen(
viewModel = podcastDetailsViewModel,
navigateToPlayer = navigateToPlayer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,6 +33,8 @@ 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 javax.inject.Inject
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
Expand All @@ -45,15 +46,14 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch

@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<PodcastInfo?>(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -105,8 +106,8 @@ import java.time.Duration
fun PlayerScreen(
windowSizeClass: WindowSizeClass,
displayFeatures: List<DisplayFeature>,
viewModel: PlayerViewModel,
onBackPress: () -> Unit
onBackPress: () -> Unit,
viewModel: PlayerViewModel = hiltViewModel(),
) {
val uiState = viewModel.uiState
PlayerScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,20 @@
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 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
Expand All @@ -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() {

Expand Down Expand Up @@ -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 <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return PlayerViewModel(episodeStore, episodePlayer, handle) as T
}
}
}
}
Loading