diff --git a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Buttons.kt b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Buttons.kt index f37d9c332..d881c10da 100644 --- a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Buttons.kt +++ b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Buttons.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme @@ -93,7 +92,7 @@ fun TvManiacOutlinedButton( borderColor: Color, modifier: Modifier = Modifier, enabled: Boolean = true, - shape: Shape = RoundedCornerShape(4.dp), + shape: Shape = MaterialTheme.shapes.small, contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit, ) { diff --git a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Card.kt b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Card.kt index 2dd7640c4..1cd86ba95 100644 --- a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Card.kt +++ b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Card.kt @@ -14,7 +14,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp @@ -26,6 +26,7 @@ fun TvPosterCard( posterImageUrl: String?, title: String, modifier: Modifier = Modifier, + shape: Shape = MaterialTheme.shapes.small, imageWidth: Dp = 120.dp, onClick: () -> Unit = {}, ) { @@ -33,7 +34,7 @@ fun TvPosterCard( modifier = modifier .width(imageWidth) .clickable { onClick() }, - shape = RectangleShape, + shape = shape, elevation = CardDefaults.cardElevation( defaultElevation = 4.dp, ), diff --git a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Dialogs.kt b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Dialogs.kt index c46fdf376..0564f8365 100644 --- a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Dialogs.kt +++ b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/components/Dialogs.kt @@ -9,7 +9,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.dp @@ -24,7 +23,7 @@ fun BasicDialog( enableConfirmButton: Boolean = true, enableDismissButton: Boolean = true, dismissButtonText: String? = null, - shape: Shape = RectangleShape, + shape: Shape = MaterialTheme.shapes.small, onDismissDialog: () -> Unit = {}, confirmButtonClicked: () -> Unit = {}, dismissButtonClicked: () -> Unit = {}, diff --git a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/theme/Shape.kt b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/theme/Shape.kt new file mode 100644 index 000000000..6e386334c --- /dev/null +++ b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.thomaskioko.tvmaniac.compose.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Shapes +import androidx.compose.ui.unit.dp + +val tvManiacShapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(8.dp), + large = RoundedCornerShape(16.dp), +) diff --git a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/theme/Theme.kt b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/theme/Theme.kt index a949932c5..398d8ae47 100644 --- a/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/theme/Theme.kt +++ b/android-core/designsystem/src/main/kotlin/com/thomaskioko/tvmaniac/compose/theme/Theme.kt @@ -54,6 +54,7 @@ fun TvManiacTheme( MaterialTheme( colorScheme = colorScheme, typography = tvManiacTypography, + shapes = tvManiacShapes, content = content, ) } diff --git a/android-core/resources/src/main/res/values/strings.xml b/android-core/resources/src/main/res/values/strings.xml index 3e72dc04e..6e8fd09b7 100644 --- a/android-core/resources/src/main/res/values/strings.xml +++ b/android-core/resources/src/main/res/values/strings.xml @@ -40,6 +40,7 @@ Settings All Seasons + All Episodes Watch Next Back Online! No Internet Connection! diff --git a/android-features/discover/src/main/java/com/thomaskioko/tvmaniac/discover/DiscoverScreen.kt b/android-features/discover/src/main/java/com/thomaskioko/tvmaniac/discover/DiscoverScreen.kt index 10f919358..02f532554 100644 --- a/android-features/discover/src/main/java/com/thomaskioko/tvmaniac/discover/DiscoverScreen.kt +++ b/android-features/discover/src/main/java/com/thomaskioko/tvmaniac/discover/DiscoverScreen.kt @@ -417,7 +417,7 @@ fun HorizontalPagerItem( } } -@OptIn(ExperimentalSnapperApi::class) +@OptIn(ExperimentalSnapperApi::class, ExperimentalFoundationApi::class) @Composable private fun RowContent( category: Category, @@ -449,6 +449,8 @@ private fun RowContent( posterImageUrl = tvShow.posterImageUrl, title = tvShow.title, onClick = { onItemClicked(tvShow.traktId) }, + modifier = Modifier + .animateItemPlacement(), ) } } diff --git a/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonDetailsScreen.kt b/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonDetailsScreen.kt index 6d78b3315..92757d8b2 100644 --- a/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonDetailsScreen.kt +++ b/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonDetailsScreen.kt @@ -7,10 +7,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api @@ -41,10 +44,14 @@ import com.thomaskioko.tvmaniac.presentation.seasondetails.Loading import com.thomaskioko.tvmaniac.presentation.seasondetails.LoadingError import com.thomaskioko.tvmaniac.presentation.seasondetails.SeasonDetailsLoaded import com.thomaskioko.tvmaniac.presentation.seasondetails.SeasonDetailsState +import com.thomaskioko.tvmaniac.presentation.seasondetails.model.Episode import com.thomaskioko.tvmaniac.presentation.seasondetails.model.SeasonDetails import com.thomaskioko.tvmaniac.resources.R import com.thomaskioko.tvmaniac.seasondetails.components.CollapsableContent -import com.thomaskioko.tvmaniac.seasondetails.components.WatchNextContent +import com.thomaskioko.tvmaniac.seasondetails.components.EpisodeItem +import dev.chrisbanes.snapper.ExperimentalSnapperApi +import dev.chrisbanes.snapper.rememberSnapperFlingBehavior +import kotlinx.collections.immutable.ImmutableList import me.tatarka.inject.annotations.Assisted import me.tatarka.inject.annotations.Inject @@ -106,8 +113,7 @@ internal fun SeasonDetailScreen( navigateUp = onBackClicked, ) }, - modifier = modifier - .statusBarsPadding(), + modifier = modifier.statusBarsPadding(), content = { contentPadding -> when (state) { Loading -> LoadingIndicator( @@ -116,14 +122,13 @@ internal fun SeasonDetailScreen( .wrapContentSize(Alignment.Center), ) - is LoadingError -> - ErrorUi( - errorMessage = state.message, - onRetry = {}, - modifier = Modifier - .fillMaxSize() - .wrapContentSize(Alignment.Center), - ) + is LoadingError -> ErrorUi( + errorMessage = state.message, + onRetry = {}, + modifier = Modifier + .fillMaxSize() + .wrapContentSize(Alignment.Center), + ) is SeasonDetailsLoaded -> { SeasonContent( @@ -155,7 +160,7 @@ private fun TopBar( @Composable private fun SeasonContent( - seasonsEpList: List?, + seasonsEpList: ImmutableList?, initialSeasonName: String?, listState: LazyListState, contentPadding: PaddingValues, @@ -179,7 +184,9 @@ private fun SeasonContent( LazyColumn( state = listState, contentPadding = contentPadding.copy(copyTop = false), - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize(), ) { item { Spacer(modifier = Modifier.height(64.dp)) } @@ -187,7 +194,11 @@ private fun SeasonContent( item { Spacer(modifier = Modifier.height(16.dp)) } - item { AllSeasonsTitle() } + item { + LabelTitle( + label = stringResource(id = R.string.title_all_episodes), + ) + } itemsIndexed(seasonsEpList) { index, season -> CollapsableContent( @@ -204,20 +215,57 @@ private fun SeasonContent( } } +@OptIn(ExperimentalSnapperApi::class) +@Composable +fun WatchNextContent( + episodeList: ImmutableList?, + modifier: Modifier = Modifier, + onEpisodeClicked: () -> Unit = {}, +) { + episodeList?.let { + LabelTitle( + modifier = modifier + .padding(top = 16.dp, bottom = 8.dp), + label = stringResource(id = R.string.title_watch_next), + ) + + val lazyListState = rememberLazyListState() + + LazyRow( + state = lazyListState, + flingBehavior = rememberSnapperFlingBehavior(lazyListState), + ) { + itemsIndexed(episodeList) { index, episode -> + val value = if (index == 0) 0 else 8 + Spacer(modifier = Modifier.width(value.dp)) + + EpisodeItem( + modifier = modifier.size(width = 320.dp, height = 90.dp), + imageUrl = episode.imageUrl, + title = episode.seasonEpisodeNumber, + episodeOverview = episode.overview, + onEpisodeClicked = onEpisodeClicked, + ) + } + + item { Spacer(modifier = Modifier.height(16.dp)) } + } + } +} + @Composable -private fun AllSeasonsTitle( +private fun LabelTitle( + label: String, modifier: Modifier = Modifier, ) { Box( - modifier = modifier - .fillMaxWidth() - .padding(2.dp), + modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center, ) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = stringResource(id = R.string.title_all_seasons), + text = label, style = MaterialTheme.typography.labelMedium.copy(MaterialTheme.colorScheme.secondary), ) } @@ -226,8 +274,7 @@ private fun AllSeasonsTitle( @ThemePreviews @Composable private fun SeasonDetailScreenPreview( - @PreviewParameter(SeasonPreviewParameterProvider::class) - state: SeasonDetailsState, + @PreviewParameter(SeasonPreviewParameterProvider::class) state: SeasonDetailsState, ) { TvManiacTheme { Surface { diff --git a/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonPreviewParameterProvider.kt b/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonPreviewParameterProvider.kt index b13afa457..5f2080937 100644 --- a/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonPreviewParameterProvider.kt +++ b/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/SeasonPreviewParameterProvider.kt @@ -6,6 +6,8 @@ import com.thomaskioko.tvmaniac.presentation.seasondetails.SeasonDetailsLoaded import com.thomaskioko.tvmaniac.presentation.seasondetails.SeasonDetailsState import com.thomaskioko.tvmaniac.presentation.seasondetails.model.Episode import com.thomaskioko.tvmaniac.presentation.seasondetails.model.SeasonDetails +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList val episode = Episode( id = 2534997, @@ -27,7 +29,7 @@ val seasonDetails = SeasonDetails( watchProgress = 0.4f, episodes = List(8) { episode - }, + }.toPersistentList(), ) class SeasonPreviewParameterProvider : PreviewParameterProvider { @@ -36,7 +38,7 @@ class SeasonPreviewParameterProvider : PreviewParameterProvider Unit = {}, ) { val transitionState = remember { @@ -112,11 +111,10 @@ private fun SeasonTitleHeader( ) Card( - shape = RectangleShape, + shape = shape, modifier = Modifier .fillMaxWidth() .height(64.dp) - .padding(horizontal = 16.dp) .clickable { onSeasonHeaderClicked() }, ) { ConstraintLayout( @@ -209,108 +207,6 @@ private fun SeasonTitleHeader( } } -@Composable -fun EpisodeItem( - episode: Episode, - modifier: Modifier = Modifier, - onEpisodeClicked: (Long) -> Unit = {}, -) { - Card( - shape = RectangleShape, - modifier = modifier - .fillMaxWidth() - .defaultMinSize(minHeight = 84.dp) - .padding(horizontal = 16.dp) - .clickable { onEpisodeClicked(episode.id) }, - ) { - ConstraintLayout( - modifier = Modifier.fillMaxWidth(), - ) { - val (episodeTitle, image, overview, watchedStatusIcon) = createRefs() - - AsyncImageComposable( - model = episode.imageUrl, - contentDescription = stringResource( - R.string.cd_show_poster, - episode.episodeNumberTitle, - ), - contentScale = ContentScale.Crop, - modifier = Modifier - .width(94.dp) - .constrainAs(image) { - start.linkTo(parent.start) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - - height = Dimension.fillToConstraints - }, - ) - - Text( - text = episode.episodeNumberTitle, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .constrainAs(episodeTitle) { - start.linkTo(image.end, 8.dp) - end.linkTo(watchedStatusIcon.start) - top.linkTo(parent.top, 8.dp) - - width = Dimension.fillToConstraints - }, - ) - - Text( - text = episode.overview, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier - .constrainAs(overview) { - start.linkTo(image.end, 8.dp) - top.linkTo(episodeTitle.bottom, 5.dp) - end.linkTo(watchedStatusIcon.start, 8.dp) - bottom.linkTo(parent.bottom, 8.dp) - - width = Dimension.fillToConstraints - }, - ) - - IconButton( - onClick = {}, - modifier = Modifier - .constrainAs(watchedStatusIcon) { - centerVerticallyTo(parent) - end.linkTo(parent.end, 8.dp) - }, - ) { - Icon( - imageVector = Icons.Filled.CheckCircle, - contentDescription = stringResource(R.string.cd_navigate_back), - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .size(32.dp), - ) - } - } - } -} - -@ThemePreviews -@Composable -fun EpisodeItemPreview() { - TvManiacTheme { - Surface { - EpisodeItem( - episode = episode, - ) - } - } -} - @ThemePreviews @Composable fun SeasonTitleHeaderPreview() { diff --git a/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/components/WatchNextList.kt b/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/components/EpisodeItem.kt similarity index 53% rename from android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/components/WatchNextList.kt rename to android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/components/EpisodeItem.kt index 57773e9b1..047a9bb7d 100644 --- a/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/components/WatchNextList.kt +++ b/android-features/season-details/src/main/java/com/thomaskioko/tvmaniac/seasondetails/components/EpisodeItem.kt @@ -1,16 +1,9 @@ package com.thomaskioko.tvmaniac.seasondetails.components import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material3.Card @@ -20,11 +13,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout @@ -32,67 +25,22 @@ import androidx.constraintlayout.compose.Dimension import com.thomaskioko.tvmaniac.compose.components.AsyncImageComposable import com.thomaskioko.tvmaniac.compose.components.ThemePreviews import com.thomaskioko.tvmaniac.compose.theme.TvManiacTheme -import com.thomaskioko.tvmaniac.presentation.seasondetails.model.Episode import com.thomaskioko.tvmaniac.resources.R import com.thomaskioko.tvmaniac.seasondetails.episode -import dev.chrisbanes.snapper.ExperimentalSnapperApi -import dev.chrisbanes.snapper.rememberSnapperFlingBehavior -@OptIn(ExperimentalSnapperApi::class) @Composable -fun WatchNextContent( - episodeList: List?, +fun EpisodeItem( + imageUrl: String?, + title: String, + episodeOverview: String, modifier: Modifier = Modifier, -) { - episodeList?.let { - Box( - modifier = modifier - .fillMaxWidth() - .padding(top = 16.dp), - contentAlignment = Alignment.Center, - ) { - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = stringResource(id = R.string.title_watch_next), - style = MaterialTheme.typography.labelMedium.copy(MaterialTheme.colorScheme.secondary), - ) - } - - Spacer(modifier = Modifier.height(8.dp)) - - val lazyListState = rememberLazyListState() - - LazyRow( - state = lazyListState, - flingBehavior = rememberSnapperFlingBehavior(lazyListState), - ) { - itemsIndexed(episodeList) { index, episode -> - val value = if (index == 0) 32 else 8 - Spacer(modifier = Modifier.width(value.dp)) - - WatchNextItem( - episode = episode, - onEpisodeClicked = {}, - ) - } - - item { Spacer(modifier = Modifier.height(16.dp)) } - } - } -} - -@Composable -fun WatchNextItem( - episode: Episode, - modifier: Modifier = Modifier, - onEpisodeClicked: (Long) -> Unit = {}, + shape: Shape = MaterialTheme.shapes.small, + onEpisodeClicked: () -> Unit = {}, ) { Card( - shape = RectangleShape, + shape = shape, modifier = modifier - .size(width = 260.dp, height = 90.dp) - .clickable { onEpisodeClicked(episode.id) }, + .clickable { onEpisodeClicked() }, ) { ConstraintLayout( modifier = Modifier.fillMaxWidth(), @@ -100,54 +48,51 @@ fun WatchNextItem( val (episodeTitle, image, overview, watchedStatusIcon) = createRefs() AsyncImageComposable( - model = episode.imageUrl, + model = imageUrl, contentDescription = stringResource( R.string.cd_show_poster, - episode.episodeNumberTitle, + title, ), contentScale = ContentScale.Crop, modifier = Modifier - .width(84.dp) + .width(94.dp) .constrainAs(image) { start.linkTo(parent.start) - bottom.linkTo(parent.bottom) top.linkTo(parent.top) + bottom.linkTo(parent.bottom) height = Dimension.fillToConstraints }, ) Text( - text = episode.seasonEpisodeNumber, + text = title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, modifier = Modifier .constrainAs(episodeTitle) { - linkTo( - start = image.end, - end = watchedStatusIcon.start, - startMargin = 8.dp, - bias = 0f, - ) - top.linkTo(parent.top, 16.dp) + start.linkTo(image.end, 8.dp) + end.linkTo(watchedStatusIcon.start) + top.linkTo(parent.top, 8.dp) - width = Dimension.preferredWrapContent + width = Dimension.fillToConstraints }, ) Text( - text = episode.episodeTitle, - maxLines = 1, + text = episodeOverview, + maxLines = 3, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium, + style = MaterialTheme.typography.bodyMedium, modifier = Modifier - .padding(bottom = 8.dp) .constrainAs(overview) { start.linkTo(image.end, 8.dp) top.linkTo(episodeTitle.bottom, 5.dp) end.linkTo(watchedStatusIcon.start, 8.dp) - bottom.linkTo(parent.bottom) + bottom.linkTo(parent.bottom, 8.dp) width = Dimension.fillToConstraints }, @@ -163,7 +108,7 @@ fun WatchNextItem( ) { Icon( imageVector = Icons.Filled.CheckCircle, - contentDescription = null, + contentDescription = stringResource(R.string.cd_navigate_back), tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier .size(32.dp), @@ -178,8 +123,10 @@ fun WatchNextItem( fun WatchlistRowItemPreview() { TvManiacTheme { Surface { - WatchNextItem( - episode = episode, + EpisodeItem( + title = episode.episodeNumberTitle, + episodeOverview = episode.overview, + imageUrl = episode.imageUrl, ) } } diff --git a/android-features/show-details/src/main/kotlin/com/thomaskioko/showdetails/ShowDetailScreen.kt b/android-features/show-details/src/main/kotlin/com/thomaskioko/showdetails/ShowDetailScreen.kt index 2b7f07cc2..c9ad89deb 100644 --- a/android-features/show-details/src/main/kotlin/com/thomaskioko/showdetails/ShowDetailScreen.kt +++ b/android-features/show-details/src/main/kotlin/com/thomaskioko/showdetails/ShowDetailScreen.kt @@ -1,6 +1,7 @@ package com.thomaskioko.showdetails import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -670,7 +671,7 @@ private fun TrailersContent( } } -@OptIn(ExperimentalSnapperApi::class) +@OptIn(ExperimentalSnapperApi::class, ExperimentalFoundationApi::class) @Composable fun SimilarShowsContent( isLoading: Boolean, @@ -696,6 +697,8 @@ fun SimilarShowsContent( Spacer(modifier = Modifier.width(value.dp)) TvPosterCard( + modifier = Modifier + .animateItemPlacement(), posterImageUrl = tvShow.posterImageUrl, title = tvShow.title, onClick = { onShowClicked(tvShow.traktId) }, diff --git a/android-features/watchlist/src/main/kotlin/com/thomaskioko/tvmaniac/watchlist/WatchlistScreen.kt b/android-features/watchlist/src/main/kotlin/com/thomaskioko/tvmaniac/watchlist/WatchlistScreen.kt index 448dcaba9..b4db906cb 100644 --- a/android-features/watchlist/src/main/kotlin/com/thomaskioko/tvmaniac/watchlist/WatchlistScreen.kt +++ b/android-features/watchlist/src/main/kotlin/com/thomaskioko/tvmaniac/watchlist/WatchlistScreen.kt @@ -1,5 +1,6 @@ package com.thomaskioko.tvmaniac.watchlist +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding @@ -113,6 +114,7 @@ private fun WatchlistScreen( ) } +@OptIn(ExperimentalFoundationApi::class) @Composable private fun FollowingGridContent( list: List, @@ -128,6 +130,8 @@ private fun FollowingGridContent( ) { show -> TvPosterCard( + modifier = Modifier + .animateItemPlacement(), posterImageUrl = show.posterImageUrl, title = show.title, onClick = { onItemClicked(show.traktId) }, diff --git a/app/src/main/kotlin/com/thomaskioko/tvmaniac/inject/ApplicationComponent.kt b/app/src/main/kotlin/com/thomaskioko/tvmaniac/inject/ApplicationComponent.kt index e18516b79..1c2518ed5 100644 --- a/app/src/main/kotlin/com/thomaskioko/tvmaniac/inject/ApplicationComponent.kt +++ b/app/src/main/kotlin/com/thomaskioko/tvmaniac/inject/ApplicationComponent.kt @@ -3,11 +3,10 @@ package com.thomaskioko.tvmaniac.inject import android.app.Application import android.content.Context import com.thomaskioko.trakt.service.implementation.inject.TraktComponent -import com.thomaskioko.trakt.service.implementation.inject.TraktPlatformComponent import com.thomaskioko.tvmaniac.TvManicApplication import com.thomaskioko.tvmaniac.data.category.implementation.CategoryComponent import com.thomaskioko.tvmaniac.data.trailers.implementation.TrailerComponent -import com.thomaskioko.tvmaniac.datastore.implementation.DataStorePlatformComponent +import com.thomaskioko.tvmaniac.datastore.implementation.DataStoreComponent import com.thomaskioko.tvmaniac.db.DatabaseComponent import com.thomaskioko.tvmaniac.episodeimages.implementation.EpisodeImageComponent import com.thomaskioko.tvmaniac.episodes.implementation.EpisodeComponent @@ -21,7 +20,6 @@ import com.thomaskioko.tvmaniac.showimages.implementation.ShowImagesComponent import com.thomaskioko.tvmaniac.shows.implementation.DiscoverComponent import com.thomaskioko.tvmaniac.similar.implementation.SimilarShowsComponent import com.thomaskioko.tvmaniac.tmdb.implementation.TmdbComponent -import com.thomaskioko.tvmaniac.tmdb.implementation.TmdbPlatformComponent import com.thomaskioko.tvmaniac.traktauth.implementation.TraktAuthComponent import com.thomaskioko.tvmaniac.traktauth.implementation.TraktAuthenticationComponent import com.thomaskioko.tvmaniac.util.inject.UtilPlatformComponent @@ -39,7 +37,7 @@ abstract class ApplicationComponent( ) : UtilPlatformComponent, CategoryComponent, DatabaseComponent, - DataStorePlatformComponent, + DataStoreComponent, EpisodeComponent, EpisodeImageComponent, WatchlistComponent, @@ -54,9 +52,7 @@ abstract class ApplicationComponent( StatsComponent, TasksComponent, TmdbComponent, - TmdbPlatformComponent, TraktComponent, - TraktPlatformComponent, TrailerComponent, TraktAuthComponent, TraktAuthenticationComponent { diff --git a/core/database/src/androidMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt b/core/database/src/androidMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt deleted file mode 100644 index 174189203..000000000 --- a/core/database/src/androidMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.thomaskioko.tvmaniac.db - -import android.app.Application -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.android.AndroidSqliteDriver -import com.thomaskioko.tvmaniac.core.db.Episode -import com.thomaskioko.tvmaniac.core.db.Episode_image -import com.thomaskioko.tvmaniac.core.db.Last_requests -import com.thomaskioko.tvmaniac.core.db.Season -import com.thomaskioko.tvmaniac.core.db.Show -import com.thomaskioko.tvmaniac.core.db.Show_category -import com.thomaskioko.tvmaniac.core.db.Show_image -import com.thomaskioko.tvmaniac.core.db.Similar_shows -import com.thomaskioko.tvmaniac.core.db.Trailers -import com.thomaskioko.tvmaniac.core.db.TvManiacDatabase -import com.thomaskioko.tvmaniac.core.db.Watchlist -import com.thomaskioko.tvmaniac.util.scope.ApplicationScope -import me.tatarka.inject.annotations.Provides - -actual interface DatabaseComponent { - - @ApplicationScope - @Provides - fun provideSqlDriver( - application: Application, - ): SqlDriver = AndroidSqliteDriver( - schema = TvManiacDatabase.Schema, - context = application, - name = "tvShows.db", - ) - - @ApplicationScope - @Provides - fun provideTvManiacDatabase( - sqlDriver: SqlDriver, - ): TvManiacDatabase = TvManiacDatabase( - driver = sqlDriver, - showAdapter = Show.Adapter( - genresAdapter = stringColumnAdapter, - idAdapter = IdAdapter(), - ), - last_requestsAdapter = Last_requests.Adapter( - timestampAdapter = InstantColumnAdapter, - ), - episode_imageAdapter = Episode_image.Adapter( - idAdapter = IdAdapter(), - tmdb_idAdapter = IdAdapter(), - ), - episodeAdapter = Episode.Adapter( - idAdapter = IdAdapter(), - season_idAdapter = IdAdapter(), - ), - seasonAdapter = Season.Adapter( - idAdapter = IdAdapter(), - show_idAdapter = IdAdapter(), - ), - show_imageAdapter = Show_image.Adapter( - idAdapter = IdAdapter(), - ), - similar_showsAdapter = Similar_shows.Adapter( - idAdapter = IdAdapter(), - similar_show_idAdapter = IdAdapter(), - ), - watchlistAdapter = Watchlist.Adapter( - idAdapter = IdAdapter(), - ), - show_categoryAdapter = Show_category.Adapter( - idAdapter = IdAdapter(), - category_idAdapter = IdAdapter(), - ), - trailersAdapter = Trailers.Adapter( - show_idAdapter = IdAdapter(), - ), - ) - - @ApplicationScope - @Provides - fun provideDbTransactionRunner( - bind: DbTransactionRunner, - ): DatabaseTransactionRunner = bind -} diff --git a/core/database/src/androidMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt b/core/database/src/androidMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt new file mode 100644 index 000000000..f1f79a2c6 --- /dev/null +++ b/core/database/src/androidMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt @@ -0,0 +1,21 @@ +package com.thomaskioko.tvmaniac.db + +import android.app.Application +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.android.AndroidSqliteDriver +import com.thomaskioko.tvmaniac.core.db.TvManiacDatabase +import com.thomaskioko.tvmaniac.util.scope.ApplicationScope +import me.tatarka.inject.annotations.Provides + +actual interface DatabasePlatformComponent { + + @ApplicationScope + @Provides + fun provideSqlDriver( + application: Application, + ): SqlDriver = AndroidSqliteDriver( + schema = TvManiacDatabase.Schema, + context = application, + name = "tvShows.db", + ) +} diff --git a/core/database/src/commonMain/kotlin/com.thomaskioko.tvmaniac.db/DatabaseComponent.kt b/core/database/src/commonMain/kotlin/com.thomaskioko.tvmaniac.db/DatabaseComponent.kt deleted file mode 100644 index 10b34033d..000000000 --- a/core/database/src/commonMain/kotlin/com.thomaskioko.tvmaniac.db/DatabaseComponent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.thomaskioko.tvmaniac.db - -expect interface DatabaseComponent diff --git a/core/database/src/iosMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt b/core/database/src/commonMain/kotlin/com.thomaskioko.tvmaniac.db/DatabasePlatformComponent.kt similarity index 91% rename from core/database/src/iosMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt rename to core/database/src/commonMain/kotlin/com.thomaskioko.tvmaniac.db/DatabasePlatformComponent.kt index ff47263d0..1c7284223 100644 --- a/core/database/src/iosMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt +++ b/core/database/src/commonMain/kotlin/com.thomaskioko.tvmaniac.db/DatabasePlatformComponent.kt @@ -1,7 +1,6 @@ package com.thomaskioko.tvmaniac.db import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.native.NativeSqliteDriver import com.thomaskioko.tvmaniac.core.db.Episode import com.thomaskioko.tvmaniac.core.db.Episode_image import com.thomaskioko.tvmaniac.core.db.Last_requests @@ -16,12 +15,9 @@ import com.thomaskioko.tvmaniac.core.db.Watchlist import com.thomaskioko.tvmaniac.util.scope.ApplicationScope import me.tatarka.inject.annotations.Provides -actual interface DatabaseComponent { - - @ApplicationScope - @Provides - fun provideSqlDriver(): SqlDriver = NativeSqliteDriver(TvManiacDatabase.Schema, "tvShows.db") +expect interface DatabasePlatformComponent +interface DatabaseComponent : DatabasePlatformComponent { @ApplicationScope @Provides fun provideTvManiacDatabase( @@ -65,4 +61,10 @@ actual interface DatabaseComponent { show_idAdapter = IdAdapter(), ), ) + + @ApplicationScope + @Provides + fun provideDbTransactionRunner( + bind: DbTransactionRunner, + ): DatabaseTransactionRunner = bind } diff --git a/core/database/src/iosMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt b/core/database/src/iosMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt new file mode 100644 index 000000000..e1816c660 --- /dev/null +++ b/core/database/src/iosMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt @@ -0,0 +1,14 @@ +package com.thomaskioko.tvmaniac.db + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.native.NativeSqliteDriver +import com.thomaskioko.tvmaniac.core.db.TvManiacDatabase +import com.thomaskioko.tvmaniac.util.scope.ApplicationScope +import me.tatarka.inject.annotations.Provides + +actual interface DatabasePlatformComponent { + + @ApplicationScope + @Provides + fun provideSqlDriver(): SqlDriver = NativeSqliteDriver(TvManiacDatabase.Schema, "tvShows.db") +} diff --git a/core/database/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt b/core/database/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt deleted file mode 100644 index 312227327..000000000 --- a/core/database/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/db/DatabaseComponent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.thomaskioko.tvmaniac.db - -actual interface DatabaseComponent diff --git a/core/database/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt b/core/database/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt new file mode 100644 index 000000000..952dcb6ec --- /dev/null +++ b/core/database/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/db/DatabasePlatformComponent.kt @@ -0,0 +1,3 @@ +package com.thomaskioko.tvmaniac.db + +actual interface DatabasePlatformComponent diff --git a/core/datastore/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt b/core/datastore/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt index dab2f748c..0e9996d76 100644 --- a/core/datastore/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt +++ b/core/datastore/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt @@ -3,7 +3,6 @@ package com.thomaskioko.tvmaniac.datastore.implementation import android.app.Application import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences -import com.thomaskioko.tvmaniac.datastore.api.DatastoreRepository import com.thomaskioko.tvmaniac.util.model.AppCoroutineScope import com.thomaskioko.tvmaniac.util.scope.ApplicationScope import me.tatarka.inject.annotations.Provides @@ -19,8 +18,4 @@ actual interface DataStorePlatformComponent { coroutineScope = scope.io, producePath = { context.filesDir.resolve(dataStoreFileName).absolutePath }, ) - - @ApplicationScope - @Provides - fun provideDatastoreRepository(bind: DatastoreRepositoryImpl): DatastoreRepository = bind } diff --git a/core/datastore/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt b/core/datastore/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt index 09e6ef4be..9c911c7c5 100644 --- a/core/datastore/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt +++ b/core/datastore/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt @@ -1,3 +1,14 @@ package com.thomaskioko.tvmaniac.datastore.implementation +import com.thomaskioko.tvmaniac.datastore.api.DatastoreRepository +import com.thomaskioko.tvmaniac.util.scope.ApplicationScope +import me.tatarka.inject.annotations.Provides + expect interface DataStorePlatformComponent + +interface DataStoreComponent : DataStorePlatformComponent { + + @ApplicationScope + @Provides + fun provideDatastoreRepository(bind: DatastoreRepositoryImpl): DatastoreRepository = bind +} diff --git a/core/datastore/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt b/core/datastore/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt index b08b6606b..3bf7f2a80 100644 --- a/core/datastore/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt +++ b/core/datastore/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/datastore/implementation/DataStorePlatformComponent.kt @@ -2,7 +2,6 @@ package com.thomaskioko.tvmaniac.datastore.implementation import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences -import com.thomaskioko.tvmaniac.datastore.api.DatastoreRepository import com.thomaskioko.tvmaniac.util.model.AppCoroutineScope import com.thomaskioko.tvmaniac.util.scope.ApplicationScope import kotlinx.cinterop.ExperimentalForeignApi @@ -32,8 +31,4 @@ actual interface DataStorePlatformComponent { requireNotNull(documentDirectory).path + "/$dataStoreFileName" }, ) - - @ApplicationScope - @Provides - fun provideDatastoreRepository(bind: DatastoreRepositoryImpl): DatastoreRepository = bind } diff --git a/core/tmdb-api/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt b/core/tmdb-api/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt index 22e3c071d..455994245 100644 --- a/core/tmdb-api/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt +++ b/core/tmdb-api/implementation/src/androidMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt @@ -4,7 +4,7 @@ import com.thomaskioko.tvmaniac.util.scope.ApplicationScope import io.ktor.client.engine.okhttp.OkHttp import me.tatarka.inject.annotations.Provides -interface TmdbPlatformComponent { +actual interface TmdbPlatformComponent { @ApplicationScope @Provides diff --git a/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbClient.kt b/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbClient.kt index 168135818..b8ac6d96e 100644 --- a/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbClient.kt +++ b/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbClient.kt @@ -52,11 +52,11 @@ fun tmdbHttpClient( } install(Logging) { - level = LogLevel.BODY + level = LogLevel.INFO logger = if (isDebug) { object : Logger { override fun log(message: String) { - kermitLogger.debug(message) + kermitLogger.info("TmbdHttp", message) } } } else { diff --git a/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbComponent.kt b/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbComponent.kt index 732c2fdfd..d621cc0d6 100644 --- a/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbComponent.kt +++ b/core/tmdb-api/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbComponent.kt @@ -14,7 +14,9 @@ typealias TmdbHttpClient = HttpClient typealias TmdbHttpClientEngine = HttpClientEngine typealias TmdbJson = Json -interface TmdbComponent { +expect interface TmdbPlatformComponent + +interface TmdbComponent : TmdbPlatformComponent { @OptIn(ExperimentalSerializationApi::class) @ApplicationScope diff --git a/core/tmdb-api/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt b/core/tmdb-api/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt index fc7eb13eb..930725e2d 100644 --- a/core/tmdb-api/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt +++ b/core/tmdb-api/implementation/src/iosMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt @@ -4,7 +4,7 @@ import com.thomaskioko.tvmaniac.util.scope.ApplicationScope import io.ktor.client.engine.darwin.Darwin import me.tatarka.inject.annotations.Provides -interface TmdbPlatformComponent { +actual interface TmdbPlatformComponent { @ApplicationScope @Provides diff --git a/core/tmdb-api/implementation/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt b/core/tmdb-api/implementation/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt index 0706afc5c..fbd673b97 100644 --- a/core/tmdb-api/implementation/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt +++ b/core/tmdb-api/implementation/src/jvmMain/kotlin/com/thomaskioko/tvmaniac/tmdb/implementation/TmdbPlatformComponent.kt @@ -1,3 +1,3 @@ package com.thomaskioko.tvmaniac.tmdb.implementation -interface TmdbPlatformComponent +actual interface TmdbPlatformComponent diff --git a/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/TraktHttpClient.kt b/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/TraktHttpClient.kt index 7cd4e27c3..056487169 100644 --- a/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/TraktHttpClient.kt +++ b/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/TraktHttpClient.kt @@ -87,7 +87,7 @@ fun traktHttpClient( logger = if (isDebug) { object : Logger { override fun log(message: String) { - kermitLogger.debug(message) + kermitLogger.info("TraktHttp", message) } } } else { diff --git a/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/inject/TraktComponent.kt b/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/inject/TraktComponent.kt index 44e786260..6f5f234ba 100644 --- a/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/inject/TraktComponent.kt +++ b/core/trakt-api/implementation/src/commonMain/kotlin/com/thomaskioko/trakt/service/implementation/inject/TraktComponent.kt @@ -21,7 +21,7 @@ import me.tatarka.inject.annotations.Provides typealias TraktHttpClient = HttpClient typealias TraktJson = Json -interface TraktComponent { +interface TraktComponent : TraktPlatformComponent { @ApplicationScope @Provides diff --git a/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageRepositoryImpl.kt b/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageRepositoryImpl.kt index fcbf7d916..441bd3eda 100644 --- a/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageRepositoryImpl.kt +++ b/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageRepositoryImpl.kt @@ -1,5 +1,6 @@ package com.thomaskioko.tvmaniac.episodeimages.implementation +import com.thomaskioko.tvmaniac.episodeimages.api.EpisodeImageDao import com.thomaskioko.tvmaniac.episodeimages.api.EpisodeImageRepository import com.thomaskioko.tvmaniac.resourcemanager.api.RequestManagerRepository import com.thomaskioko.tvmaniac.util.model.AppCoroutineDispatchers @@ -20,19 +21,23 @@ class EpisodeImageRepositoryImpl( private val dispatchers: AppCoroutineDispatchers, private val requestManagerRepository: RequestManagerRepository, private val store: EpisodeImageStore, + private val episodeImageDao: EpisodeImageDao, ) : EpisodeImageRepository { override fun updateEpisodeImage(traktId: Long): Flow> = - store.stream( - StoreReadRequest.cached( - key = traktId, - refresh = requestManagerRepository.isRequestExpired( - entityId = traktId, - requestType = "EPISODE_IMAGE", - threshold = 1.hours, - ), - ), - ) + episodeImageDao.observeEpisodeImage(traktId) + .flatMapLatest { + store.stream( + StoreReadRequest.cached( + key = traktId, + refresh = requestManagerRepository.isRequestExpired( + entityId = traktId, + requestType = "EPISODE_IMAGE", + threshold = 1.hours, + ), + ), + ) + } .flatMapLatest { flowOf(Either.Right(Unit)) } .flowOn(dispatchers.io) } diff --git a/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageStore.kt b/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageStore.kt index 05b40b00e..27c9fd74a 100644 --- a/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageStore.kt +++ b/data/episodeimages/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/episodeimages/implementation/EpisodeImageStore.kt @@ -31,7 +31,6 @@ class EpisodeImageStore( private val scope: AppCoroutineScope, ) : Store> by StoreBuilder.from( fetcher = Fetcher.of { id -> - episodeImageDao.getEpisodeImage(id) .filter { it.tmdb_id != null && it.image_url == null } .map { episodeArt -> diff --git a/data/seasondetails/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/seasondetails/implementation/SeasonDetailsStore.kt b/data/seasondetails/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/seasondetails/implementation/SeasonDetailsStore.kt index ff39ad426..d8f961290 100644 --- a/data/seasondetails/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/seasondetails/implementation/SeasonDetailsStore.kt +++ b/data/seasondetails/implementation/src/commonMain/kotlin/com/thomaskioko/tvmaniac/seasondetails/implementation/SeasonDetailsStore.kt @@ -2,6 +2,7 @@ package com.thomaskioko.tvmaniac.seasondetails.implementation import com.thomaskioko.tvmaniac.core.db.Season import com.thomaskioko.tvmaniac.core.db.SeasonEpisodeDetailsById +import com.thomaskioko.tvmaniac.db.DbTransactionRunner import com.thomaskioko.tvmaniac.db.Id import com.thomaskioko.tvmaniac.episodes.api.EpisodesDao import com.thomaskioko.tvmaniac.seasons.api.SeasonsDao @@ -21,6 +22,7 @@ class SeasonDetailsStore( private val seasonCache: SeasonsDao, private val episodesDao: EpisodesDao, private val scope: AppCoroutineScope, + private val dbTransactionRunner: DbTransactionRunner, private val logger: KermitLogger, ) : Store> by StoreBuilder .from( @@ -46,19 +48,21 @@ class SeasonDetailsStore( sourceOfTruth = SourceOfTruth.of( reader = seasonCache::observeSeasonEpisodeDetailsById, writer = { id, list -> - list.forEach { season -> - seasonCache.upsert( - Season( - id = Id(season.seasonId), - show_id = Id(id), - season_number = season.seasonNumber, - title = season.title, - episode_count = season.episodeCount, - overview = season.overview, - ), - ) + dbTransactionRunner { + list.forEach { season -> + seasonCache.upsert( + Season( + id = Id(season.seasonId), + show_id = Id(id), + season_number = season.seasonNumber, + title = season.title, + episode_count = season.episodeCount, + overview = season.overview, + ), + ) - episodesDao.insert(season.episodes) + episodesDao.insert(season.episodes) + } } }, delete = seasonCache::delete, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb32e8463..9da524c3a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] accompanist = "0.33.2-alpha" -agp = "8.1.3" -androidx-activity = "1.8.0" -androidx-browser = "1.6.0" +agp = "8.1.4" +androidx-activity = "1.8.1" +androidx-browser = "1.7.0" androidx-core = "1.12.0" androidx-core-splashscreen = "1.0.1" androidx-datastore = "1.1.0-alpha06" @@ -13,7 +13,7 @@ androidx-palette = "1.0.0" androidx-work = "2.8.1" appauth = "0.11.1" atomicfu = "0.22.0" -coil = "2.4.0" +coil = "2.5.0" compose-bom = "2023.10.01" compose-constraintlayout = "1.0.1" compose-paging = "3.2.1" @@ -23,7 +23,7 @@ datetime = "0.4.1" dependency-analysis = "1.25.0" dependency-check = "0.49.0" desugar = "2.0.4" -detekt = "1.23.0" +detekt = "1.23.3" flowredux = "1.2.0" kenburns = "1.0.7" kermit = "1.2.3" @@ -32,8 +32,8 @@ kotest = "5.5.4" kotlin = "1.9.20" kotlininject = "0.6.3" kotlinx-collections = "0.3.6" -ksp = "1.9.20-1.0.14" -ktor = "2.3.5" +ksp = "1.9.20-1.0.13" +ktor = "2.3.6" lint = "1.2.0" moko-resources = "0.23.0" shared-module-version = "0.8.0" diff --git a/presentation/seasondetails/build.gradle.kts b/presentation/seasondetails/build.gradle.kts index 797a389a7..1fb41a044 100644 --- a/presentation/seasondetails/build.gradle.kts +++ b/presentation/seasondetails/build.gradle.kts @@ -13,6 +13,8 @@ kotlin { implementation(projects.data.episodes.api) implementation(projects.data.seasondetails.api) + api(libs.kotlinx.collections) + implementation(libs.flowredux) implementation(libs.kotlinInject.runtime) } diff --git a/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/Mapper.kt b/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/Mapper.kt index e04b38087..ec0b75584 100644 --- a/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/Mapper.kt +++ b/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/Mapper.kt @@ -3,18 +3,24 @@ package com.thomaskioko.tvmaniac.presentation.seasondetails import com.thomaskioko.tvmaniac.core.db.SeasonEpisodeDetailsById import com.thomaskioko.tvmaniac.presentation.seasondetails.model.Episode import com.thomaskioko.tvmaniac.presentation.seasondetails.model.SeasonDetails +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList -fun List?.toSeasonWithEpisodes(): List { - return this?.groupBy { it.season_id }?.map { (_, groupMap) -> - val seasonRow = groupMap.first() - SeasonDetails( - seasonId = seasonRow.season_id.id, - seasonName = seasonRow.season_title, - episodes = groupMap.map { it.toEpisode() }, - episodeCount = seasonRow.episode_count, - watchProgress = 0f, - ) - } ?: emptyList() +fun List?.toSeasonWithEpisodes(): PersistentList { + return this + ?.groupBy { it.season_id } + ?.map { (_, groupMap) -> + val seasonRow = groupMap.first() + SeasonDetails( + seasonId = seasonRow.season_id.id, + seasonName = seasonRow.season_title, + episodes = groupMap.map { it.toEpisode() }.toImmutableList(), + episodeCount = seasonRow.episode_count, + watchProgress = 0f, + ) + }?.toPersistentList() ?: persistentListOf() } fun SeasonEpisodeDetailsById.toEpisode(): Episode { diff --git a/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/SeasonDetailsState.kt b/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/SeasonDetailsState.kt index 184c48226..449f31bcf 100644 --- a/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/SeasonDetailsState.kt +++ b/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/SeasonDetailsState.kt @@ -1,14 +1,16 @@ package com.thomaskioko.tvmaniac.presentation.seasondetails import com.thomaskioko.tvmaniac.presentation.seasondetails.model.SeasonDetails +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf sealed interface SeasonDetailsState -object Loading : SeasonDetailsState +data object Loading : SeasonDetailsState data class SeasonDetailsLoaded( val showTitle: String = "", - val seasonDetailsList: List = emptyList(), + val seasonDetailsList: PersistentList = persistentListOf(), val errorMessage: String? = null, ) : SeasonDetailsState diff --git a/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/model/SeasonDetails.kt b/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/model/SeasonDetails.kt index b46347ed2..312eeeb06 100644 --- a/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/model/SeasonDetails.kt +++ b/presentation/seasondetails/src/commonMain/kotlin/com/thomaskioko/tvmaniac/presentation/seasondetails/model/SeasonDetails.kt @@ -1,9 +1,11 @@ package com.thomaskioko.tvmaniac.presentation.seasondetails.model +import kotlinx.collections.immutable.ImmutableList + data class SeasonDetails( val seasonId: Long, val seasonName: String, val episodeCount: Long, val watchProgress: Float, - val episodes: List, + val episodes: ImmutableList, ) diff --git a/presentation/seasondetails/src/commonTest/kotlin/com/thomaskioko/tvmaniac/data/seasondetails/MockData.kt b/presentation/seasondetails/src/commonTest/kotlin/com/thomaskioko/tvmaniac/data/seasondetails/MockData.kt index 261f3f860..73f19a495 100644 --- a/presentation/seasondetails/src/commonTest/kotlin/com/thomaskioko/tvmaniac/data/seasondetails/MockData.kt +++ b/presentation/seasondetails/src/commonTest/kotlin/com/thomaskioko/tvmaniac/data/seasondetails/MockData.kt @@ -3,8 +3,9 @@ package com.thomaskioko.tvmaniac.data.seasondetails import com.thomaskioko.tvmaniac.presentation.seasondetails.SeasonDetailsLoaded import com.thomaskioko.tvmaniac.presentation.seasondetails.model.Episode import com.thomaskioko.tvmaniac.presentation.seasondetails.model.SeasonDetails +import kotlinx.collections.immutable.persistentListOf -val episodes = listOf( +val episodes = persistentListOf( Episode( id = 12345, seasonId = 12343, @@ -19,7 +20,7 @@ val episodes = listOf( ), ) -val seasonDetailsList = listOf( +val seasonDetailsList = persistentListOf( SeasonDetails( seasonId = 12343, seasonName = "Season 01", diff --git a/shared/src/iosMain/kotlin/com.thomaskioko.tvmaniac.shared/base/ApplicationComponent.kt b/shared/src/iosMain/kotlin/com.thomaskioko.tvmaniac.shared/base/ApplicationComponent.kt index 36e7baf91..2af826c9b 100644 --- a/shared/src/iosMain/kotlin/com.thomaskioko.tvmaniac.shared/base/ApplicationComponent.kt +++ b/shared/src/iosMain/kotlin/com.thomaskioko.tvmaniac.shared/base/ApplicationComponent.kt @@ -1,10 +1,9 @@ package com.thomaskioko.tvmaniac.shared.base import com.thomaskioko.trakt.service.implementation.inject.TraktComponent -import com.thomaskioko.trakt.service.implementation.inject.TraktPlatformComponent import com.thomaskioko.tvmaniac.data.category.implementation.CategoryComponent import com.thomaskioko.tvmaniac.data.trailers.implementation.TrailerComponent -import com.thomaskioko.tvmaniac.datastore.implementation.DataStorePlatformComponent +import com.thomaskioko.tvmaniac.datastore.implementation.DataStoreComponent import com.thomaskioko.tvmaniac.db.DatabaseComponent import com.thomaskioko.tvmaniac.episodeimages.implementation.EpisodeImageComponent import com.thomaskioko.tvmaniac.episodes.implementation.EpisodeComponent @@ -24,7 +23,6 @@ import com.thomaskioko.tvmaniac.showimages.implementation.ShowImagesComponent import com.thomaskioko.tvmaniac.shows.implementation.DiscoverComponent import com.thomaskioko.tvmaniac.similar.implementation.SimilarShowsComponent import com.thomaskioko.tvmaniac.tmdb.implementation.TmdbComponent -import com.thomaskioko.tvmaniac.tmdb.implementation.TmdbPlatformComponent import com.thomaskioko.tvmaniac.traktauth.implementation.TraktAuthenticationComponent import com.thomaskioko.tvmaniac.util.inject.UtilPlatformComponent import com.thomaskioko.tvmaniac.util.scope.ApplicationScope @@ -36,7 +34,7 @@ import me.tatarka.inject.annotations.Component abstract class ApplicationComponent : CategoryComponent, DatabaseComponent, - DataStorePlatformComponent, + DataStoreComponent, EpisodeComponent, EpisodeImageComponent, ProfileComponent, @@ -48,9 +46,7 @@ abstract class ApplicationComponent : SimilarShowsComponent, StatsComponent, TmdbComponent, - TmdbPlatformComponent, TraktComponent, - TraktPlatformComponent, TraktAuthenticationComponent, TrailerComponent, UtilPlatformComponent,