From 76083761a9048fb3a7ea36e1a9f65ff2dc0a28d2 Mon Sep 17 00:00:00 2001 From: Owen LeJeune Date: Tue, 13 Jun 2023 20:47:20 -0400 Subject: [PATCH] add watch provider info --- app/build.gradle | 3 +- .../owenlejeune/tvtime/TvTimeApplication.kt | 4 +- .../tvtime/api/tmdb/api/v3/DetailService.kt | 2 + .../tvtime/api/tmdb/api/v3/MoviesApi.kt | 3 + .../tvtime/api/tmdb/api/v3/MoviesService.kt | 5 + .../tvtime/api/tmdb/api/v3/TvApi.kt | 3 + .../tvtime/api/tmdb/api/v3/TvService.kt | 5 + .../api/tmdb/api/v3/model/DetailedMovie.kt | 2 +- .../api/tmdb/api/v3/model/WatchProviders.kt | 32 ++ .../tvtime/api/tmdb/api/v4/model/MediaList.kt | 2 +- .../owenlejeune/tvtime/di/modules/modules.kt | 8 + .../tvtime/extensions/ListExtensions.kt | 9 + .../owenlejeune/tvtime/ui/components/Cards.kt | 291 +----------------- .../tvtime/ui/components/DetailViewCommon.kt | 2 + .../tvtime/ui/components/Overlays.kt | 3 + .../tvtime/ui/components/Posters.kt | 80 +---- .../tvtime/ui/components/TabsCommon.kt | 2 +- .../tvtime/ui/components/Widgets.kt | 67 ++++ .../tvtime/ui/screens/AccountScreen.kt | 12 +- .../tvtime/ui/screens/ListDetailScreen.kt | 4 +- .../tvtime/ui/screens/MediaDetailScreen.kt | 156 +++++++++- .../tvtime/ui/screens/PeopleDetailScreen.kt | 8 +- .../tvtime/ui/screens/SearchScreen.kt | 2 +- .../tvtime/ui/screens/SettingsScreen.kt | 3 +- .../com/owenlejeune/tvtime/utils/TmdbUtils.kt | 30 +- app/src/main/res/values/strings.xml | 4 + 26 files changed, 344 insertions(+), 398 deletions(-) create mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/WatchProviders.kt create mode 100644 app/src/main/java/com/owenlejeune/tvtime/extensions/ListExtensions.kt diff --git a/app/build.gradle b/app/build.gradle index 2928aed..48af2d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -114,8 +114,9 @@ dependencies { implementation "com.localebro:okhttpprofiler:$profiler" // koin - def koin = "3.1.4" + def koin = "3.3.0" implementation "io.insert-koin:koin-android:$koin" + implementation "io.insert-koin:koin-androidx-compose:$koin" // coil def coil = "2.2.2" diff --git a/app/src/main/java/com/owenlejeune/tvtime/TvTimeApplication.kt b/app/src/main/java/com/owenlejeune/tvtime/TvTimeApplication.kt index 7b8a7b4..581cdb2 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/TvTimeApplication.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/TvTimeApplication.kt @@ -6,6 +6,7 @@ import com.kieronquinn.monetcompat.core.MonetCompat import com.owenlejeune.tvtime.di.modules.appModule import com.owenlejeune.tvtime.di.modules.networkModule import com.owenlejeune.tvtime.di.modules.preferencesModule +import com.owenlejeune.tvtime.di.modules.viewModelModule import com.owenlejeune.tvtime.preferences.AppPreferences import dev.kdrag0n.monet.factory.ColorSchemeFactory import org.koin.android.ext.android.inject @@ -30,7 +31,8 @@ class TvTimeApplication: Application() { modules( networkModule, preferencesModule, - appModule + appModule, + viewModelModule ) } diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/DetailService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/DetailService.kt index 21e9f75..906f9c7 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/DetailService.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/DetailService.kt @@ -23,4 +23,6 @@ interface DetailService { suspend fun getKeywords(id: Int): Response + suspend fun getWatchProviders(id: Int): Response + } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesApi.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesApi.kt index 8a9760a..d0c6e28 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesApi.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesApi.kt @@ -55,4 +55,7 @@ interface MoviesApi { @Query("session_id") sessionId: String ): Response + @GET("movie/{id}/watch/providers") + suspend fun getWatchProviders(@Path("id") id: Int): Response + } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesService.kt index 3cdb072..83c0631 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesService.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/MoviesService.kt @@ -11,6 +11,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse import com.owenlejeune.tvtime.utils.SessionManager import org.koin.core.component.KoinComponent import retrofit2.Response @@ -77,4 +78,8 @@ class MoviesService: KoinComponent, DetailService, HomePageService { return movieService.getKeywords(id) } + override suspend fun getWatchProviders(id: Int): Response { + return movieService.getWatchProviders(id) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvApi.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvApi.kt index f028478..866ac89 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvApi.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvApi.kt @@ -58,4 +58,7 @@ interface TvApi { @GET("tv/{id}/season/{season}") suspend fun getSeason(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response + @GET("tv/{id}/watch/providers") + suspend fun getWatchProviders(@Path("id") seriesId: Int): Response + } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvService.kt index a057ff0..63293e3 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvService.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/TvService.kt @@ -12,6 +12,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Season import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse import com.owenlejeune.tvtime.utils.SessionManager import org.koin.core.component.KoinComponent import retrofit2.Response @@ -78,6 +79,10 @@ class TvService: KoinComponent, DetailService, HomePageService { return service.getKeywords(id) } + override suspend fun getWatchProviders(id: Int): Response { + return service.getWatchProviders(id) + } + suspend fun getSeason(seriesId: Int, seasonId: Int): Response { return service.getSeason(seriesId, seasonId) } diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/DetailedMovie.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/DetailedMovie.kt index cf2b37a..f05a309 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/DetailedMovie.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/DetailedMovie.kt @@ -22,7 +22,7 @@ class DetailedMovie( @SerializedName("adult") val isAdult: Boolean, @SerializedName("budget") val budget: Int, @SerializedName("release_date") val releaseDate: String, - @SerializedName("revenue") val revenue: Int, + @SerializedName("revenue") val revenue: Long, @SerializedName("runtime") val runtime: Int?, @SerializedName("imdb_id") val imdbId: String?, @SerializedName("belongs_to_collection") val collection: Collection? diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/WatchProviders.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/WatchProviders.kt new file mode 100644 index 0000000..0d047c4 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/WatchProviders.kt @@ -0,0 +1,32 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName + +data class WatchProviderResponse( + @SerializedName("id") + val id: Int, + @SerializedName("results") + val results: Map +) + +data class WatchProviders( + @SerializedName("link") + val link: String, + @SerializedName("flatrate") + val flaterate: List?, + @SerializedName("rent") + val rent: List?, + @SerializedName("buy") + val buy: List? +) + +data class WatchProviderDetails( + @SerializedName("logo_path") + val logoPath: String, + @SerializedName("provider_id") + val providerId: Int, + @SerializedName("provider_name") + val providerName: String, + @SerializedName("display_priority") + val displayPriority: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/model/MediaList.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/model/MediaList.kt index 029bad5..a0a0124 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/model/MediaList.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/model/MediaList.kt @@ -18,7 +18,7 @@ class MediaList( @SerializedName("average_rating") val averageRating: Float, @SerializedName("runtime") val runtime: Int, @SerializedName("name") val name: String, - @SerializedName("revenue") val revenue: Int, + @SerializedName("revenue") val revenue: Long, @SerializedName("sort_by") val sortBy: SortOrder ) diff --git a/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt b/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt index e2c17e7..44438dd 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt @@ -14,7 +14,10 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service import com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer.ListItemDeserializer import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem import com.owenlejeune.tvtime.preferences.AppPreferences +import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel +import com.owenlejeune.tvtime.ui.viewmodel.SettingsViewModel import com.owenlejeune.tvtime.utils.ResourceUtils +import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val networkModule = module { @@ -61,4 +64,9 @@ val preferencesModule = module { val appModule = module { factory { ResourceUtils(get()) } +} + +val viewModelModule = module { + viewModel { ConfigurationViewModel() } + viewModel { SettingsViewModel() } } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/extensions/ListExtensions.kt b/app/src/main/java/com/owenlejeune/tvtime/extensions/ListExtensions.kt new file mode 100644 index 0000000..c335b63 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/extensions/ListExtensions.kt @@ -0,0 +1,9 @@ +package com.owenlejeune.tvtime.extensions + +fun List.lastOr(provider: () -> T): T { + return if (isNotEmpty()) { + last() + } else { + provider() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt index f56cb83..315e750 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt @@ -62,7 +62,7 @@ fun ContentCard( Text( text = title, style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 16.dp, top = 8.dp), + modifier = Modifier.padding(start = 16.dp, top = 12.dp), color = textColor ) } @@ -119,41 +119,6 @@ fun ExpandableContentCard( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun LazyListContentCard( - modifier: Modifier = Modifier, - header: @Composable (() -> Unit)? = null, - footer: @Composable (() -> Unit)? = null, - backgroundColor: Color = MaterialTheme.colorScheme.surfaceVariant, - content: LazyListScope.() -> Unit -) { - Card( - modifier = modifier, - shape = RoundedCornerShape(10.dp), - elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), - colors = CardDefaults.cardColors(containerColor = backgroundColor) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - header?.invoke() - val listState = rememberLazyListState() - LazyColumn( - content = content, - modifier = Modifier - .fillMaxWidth() - .weight(1f), - state = listState - ) - footer?.invoke() - } - } -} - @OptIn(ExperimentalMaterial3Api::class) @Composable fun ListContentCard( @@ -226,258 +191,4 @@ fun TwoLineImageTextCard( ) } } -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun SwipeableActionCard( - modifier: Modifier = Modifier, - leftSwipeCard: (@Composable () -> Unit)? = null, - rightSwipeCard: (@Composable () -> Unit)? = null, - leftSwiped: () -> Unit = {}, - rightSwiped: () -> Unit = {}, - animationSpec: AnimationSpec = tween(250), - velocityThreshold: Dp = 125.dp, -// shape: Shape = RectangleShape, - mainContent: @Composable () -> Unit -) { - val coroutineScope = rememberCoroutineScope() - - val thresholds = { _: SwipeCardState, _: SwipeCardState -> - FractionalThreshold(0.6f) - } - - Box( - modifier = modifier//.clip(shape) - ) { - val swipeableState = rememberSwipeableState( - initialValue = SwipeCardState.DEFAULT, - animationSpec = animationSpec - ) - - val swipeLeftCardVisible = remember { mutableStateOf(false) } - val swipeEnabled = remember { mutableStateOf(true) } - val maxWidthInPx = with(LocalDensity.current) { - LocalConfiguration.current.screenWidthDp.dp.toPx() - } - - val anchors = hashMapOf(0f to SwipeCardState.DEFAULT) - leftSwipeCard?.let { anchors[-maxWidthInPx] = SwipeCardState.LEFT } - rightSwipeCard?.let { anchors[maxWidthInPx] = SwipeCardState.RIGHT } - - Surface( - color = Color.Transparent, - content = if (swipeLeftCardVisible.value) { - leftSwipeCard - } else { - rightSwipeCard - } ?: {} - ) - - Surface( - color = Color.Transparent, - modifier = Modifier - .fillMaxWidth() - .offset { - var offset = swipeableState.offset.value.roundToInt() - if (offset < 0 && leftSwipeCard == null) offset = 0 - if (offset > 0 && rightSwipeCard == null) offset = 0 - IntOffset(offset, 0) - } - .swipeable( - state = swipeableState, - anchors = anchors, - orientation = Orientation.Horizontal, - enabled = swipeEnabled.value, - thresholds = thresholds, - velocityThreshold = velocityThreshold - ) - ) { - val resetStateToDefault = { - coroutineScope.launch { - swipeEnabled.value = false - swipeableState.animateTo(SwipeCardState.DEFAULT) - swipeEnabled.value = true - } - } - when { - swipeableState.currentValue == SwipeCardState.LEFT && !swipeableState.isAnimationRunning -> { - leftSwiped() - LaunchedEffect(Unit) { - resetStateToDefault() - } - } - swipeableState.currentValue == SwipeCardState.RIGHT && !swipeableState.isAnimationRunning -> { - rightSwiped() - LaunchedEffect(Unit) { - resetStateToDefault() - } - } - } - - swipeLeftCardVisible.value = swipeableState.offset.value <= 0 - - mainContent() - } - } -} - -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) -@Composable -fun DraggableCard( - modifier: Modifier = Modifier, - cardOffset: Float, - isRevealed: Boolean, - cardElevation: CardElevation = CardDefaults.cardElevation(defaultElevation = 10.dp), - backgroundColor: Color = MaterialTheme.colorScheme.surfaceVariant, - shape: Shape = RectangleShape, - onExpand: () -> Unit = {}, - onCollapse: () -> Unit = {}, - backgroundContent: @Composable () -> Unit, - content: @Composable ColumnScope.() -> Unit -) { - val coroutineScope = rememberCoroutineScope() - - val animationDuration = 500 - val velicityThreshold = 125.dp - - val thresholds: (from: SwipeCardState, to: SwipeCardState) -> ThresholdConfig = { _, _ -> - FractionalThreshold(0.6f) - } - - val swipeableState = rememberSwipeableState( - initialValue = SwipeCardState.DEFAULT, - animationSpec = tween(animationDuration) - ) - - val maxWidthPx = with(LocalDensity.current) { - LocalConfiguration.current.screenWidthDp.dp.toPx() - } - - val anchors = hashMapOf(0f to SwipeCardState.DEFAULT) - anchors[-maxWidthPx] = SwipeCardState.LEFT - - val swipeEnabled = remember { mutableStateOf(true) } - - Box ( - modifier = Modifier.clip(shape = shape) - ) { - backgroundContent() - - Card( - shape = shape, - elevation = cardElevation, - modifier = modifier - .background(color = backgroundColor) - .offset { - var offset = swipeableState.offset.value.roundToInt() - if (offset < 0) offset = 0 - IntOffset(offset, 0) - } - .swipeable( - state = swipeableState, - anchors = anchors, - orientation = Orientation.Horizontal, - enabled = swipeEnabled.value, - thresholds = thresholds, - velocityThreshold = velicityThreshold - ) - ) { - if (swipeableState.currentValue == SwipeCardState.LEFT && !swipeableState.isAnimationRunning) { - onExpand() - LaunchedEffect(key1 = Unit) { - coroutineScope.launch { - swipeEnabled.value = false - swipeableState.animateTo(SwipeCardState.DEFAULT) - swipeEnabled.value = true - } - } - } - content() - } -// DraggableCardInternal( -// modifier = modifier, -// cardOffset = cardOffset, -// isRevealed = isRevealed, -// cardElevation = cardElevation, -// backgroundColor = backgroundColor, -// shape = shape, -// onExpand = onExpand, -// onCollapse = onCollapse, -// content = content -// ) - } -} - -private enum class SwipeCardState { - DEFAULT, - LEFT, - RIGHT -} - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) -@SuppressLint("UnusedTransitionTargetStateParameter") -@Composable -fun DraggableCardInternal( - modifier: Modifier = Modifier, - cardOffset: Float, - isRevealed: Boolean, - cardElevation: CardElevation = CardDefaults.cardElevation(defaultElevation = 10.dp), - backgroundColor: Color = MaterialTheme.colorScheme.surfaceVariant, - shape: Shape = RectangleShape, - onExpand: () -> Unit = {}, - onCollapse: () -> Unit = {}, - content: @Composable ColumnScope.() -> Unit -) { - val animationDuration = 500 - - val swipeableState = rememberSwipeableState( - initialValue = SwipeCardState.DEFAULT, - animationSpec = tween(animationDuration) - ) - - val maxWidthPx = with(LocalDensity.current) { - LocalConfiguration.current.screenWidthDp.dp.toPx() - } - - val anchors = hashMapOf(0f to SwipeCardState.DEFAULT) - anchors[-maxWidthPx] = SwipeCardState.LEFT - - Card( - modifier = modifier - .background(color = backgroundColor), - shape = shape, - elevation = cardElevation - ){} -// val animationDuration = 500 -// val minDragAmount = 5 -// -// val transitionState = remember { -// MutableTransitionState(isRevealed).apply { -// targetState = !isRevealed -// } -// } -// val transition = updateTransition(targetState = transitionState, "cardTransition") -// val offsetTransition by transition.animateFloat( -// label = "cardOffsetTransition", -// transitionSpec = { tween(durationMillis = animationDuration) }, -// targetValueByState = { if (isRevealed) cardOffset else 0f }, -// ) -// -// Card( -// modifier = modifier -// .background(color = backgroundColor) -// .offset { IntOffset(offsetTransition.roundToInt(), 0) } -// .pointerInput(Unit) { -// detectHorizontalDragGestures { _, dragAmount -> -// when { -// dragAmount >= minDragAmount -> onExpand() -// dragAmount < -minDragAmount -> onCollapse() -// } -// } -// }, -// shape = shape, -// content = content -// ) - } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/DetailViewCommon.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/DetailViewCommon.kt index 3d445ac..0dffd35 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/DetailViewCommon.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/DetailViewCommon.kt @@ -25,9 +25,11 @@ import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.rememberPagerState import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection +import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel import com.owenlejeune.tvtime.utils.TmdbUtils import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalPagerApi::class) @Composable diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Overlays.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Overlays.kt index d49d041..acb493a 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Overlays.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Overlays.kt @@ -7,13 +7,16 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.rememberPagerState import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection +import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel import com.owenlejeune.tvtime.utils.TmdbUtils +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalPagerApi::class) @Composable diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt index 6a2173a..5dbe198 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt @@ -2,14 +2,12 @@ package com.owenlejeune.tvtime.ui.components import android.util.Log import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.magnifier import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BrokenImage @@ -28,10 +26,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize @@ -39,8 +35,6 @@ import androidx.compose.ui.unit.dp import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import coil.compose.AsyncImage -import coil.compose.rememberAsyncImagePainter -import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePerson import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Person import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem @@ -48,35 +42,14 @@ import com.owenlejeune.tvtime.extensions.header import com.owenlejeune.tvtime.extensions.lazyPagingItems import com.owenlejeune.tvtime.extensions.listItems import com.owenlejeune.tvtime.preferences.AppPreferences +import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel import com.owenlejeune.tvtime.utils.TmdbUtils +import org.koin.androidx.compose.koinViewModel import org.koin.java.KoinJavaComponent.get private val POSTER_WIDTH = 120.dp private val POSTER_HEIGHT = 180.dp -@Composable -fun PosterGrid( - fetchMedia: (MutableState>) -> Unit = {}, - onClick: (Int) -> Unit = {} -) { - val mediaList = remember { mutableStateOf(emptyList()) } - fetchMedia(mediaList) - - LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = POSTER_WIDTH), - contentPadding = PaddingValues(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - listItems(mediaList.value) { item -> - PosterItem( - modifier = Modifier.padding(5.dp), - mediaItem = item, - onClick = onClick - ) - } - } -} - @Composable fun PagingPosterGrid( lazyPagingItems: LazyPagingItems?, @@ -148,34 +121,6 @@ fun PagingPeoplePosterGrid( } } -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun PeoplePosterGrid( - fetchPeople: (MutableState>) -> Unit = {}, - onClick: (Int) -> Unit = {} -) { - val peopleList = remember { mutableStateOf(emptyList()) } - fetchPeople(peopleList) - - LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = POSTER_WIDTH), - contentPadding = PaddingValues(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - listItems(peopleList.value) { person -> - PosterItem( - url = TmdbUtils.getFullPersonImagePath(person.profilePath), - placeholder = Icons.Filled.Person, - modifier = Modifier.padding(5.dp), - onClick = { - onClick(person.id) - }, - title = person.name - ) - } - } -} - @Composable fun PosterItem( modifier: Modifier = Modifier, @@ -198,27 +143,6 @@ fun PosterItem( ) } -@Composable -fun PosterItem( - modifier: Modifier = Modifier, - width: Dp = POSTER_WIDTH, - onClick: (id: Int) -> Unit = {}, - person: Person? -) { - PosterItem( - modifier = modifier, - width = width, - onClick = { - person?.let { - onClick(person.id) - } - }, - url = person?.let { TmdbUtils.getFullPersonImagePath(person) }, - elevation = 8.dp, - title = person?.name - ) -} - @OptIn(ExperimentalMaterial3Api::class) @Composable fun PosterItem( diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/TabsCommon.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/TabsCommon.kt index eb053f6..4929cbc 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/TabsCommon.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/TabsCommon.kt @@ -122,7 +122,7 @@ fun ScrollableTabs( } @Composable -private fun SmallTabIndicator( +fun SmallTabIndicator( modifier: Modifier = Modifier, color: Color = MaterialTheme.colorScheme.primary ) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt index cd1d5ce..3196263 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource @@ -66,6 +67,7 @@ import coil.compose.rememberAsyncImagePainter import com.google.accompanist.flowlayout.FlowRow import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails +import com.owenlejeune.tvtime.extensions.toDp import com.owenlejeune.tvtime.extensions.unlessEmpty import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.ui.navigation.AppNavItem @@ -1099,4 +1101,69 @@ fun SearchBar( @Composable fun MyDivider(modifier: Modifier = Modifier) { Divider(thickness = 0.5.dp, modifier = modifier, color = MaterialTheme.colorScheme.secondaryContainer) +} + +@Composable +fun SelectableTextItem( + selected: Boolean, + onSelected: () -> Unit, + text: String, + selectedColor: Color = MaterialTheme.colorScheme.secondary, + unselectedColor: Color = MaterialTheme.colorScheme.onSurfaceVariant +) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(10.dp)) + .clickable(onClick = onSelected) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(6.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(8.dp) + ) { + val size = remember { mutableStateOf(IntSize.Zero) } + val color = if (selected) selectedColor else unselectedColor + Text( + text = text, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, + color = color, + modifier = Modifier + .padding(horizontal = 4.dp) + .onGloballyPositioned { size.value = it.size } + ) + Box( + modifier = Modifier + .height(height = if (selected) 3.dp else 1.dp) + .width(width = size.value.width.toDp().plus(8.dp)) + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) + .background(color = color) + ) + } + } +} + +@Composable +fun SelectableTextChip( + selected: Boolean, + onSelected: () -> Unit, + text: String, + selectedColor: Color = MaterialTheme.colorScheme.secondary, + unselectedColor: Color = MaterialTheme.colorScheme.surfaceVariant +) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(percent = 25)) + .border(width = 1.dp, color = selectedColor, shape = RoundedCornerShape(percent = 25)) + .background(color = if(selected) selectedColor else unselectedColor) + .clickable(onClick = onSelected) + ) { + Text( + text = text, + color = if (selected) unselectedColor else selectedColor, + fontSize = 16.sp, + modifier = Modifier + .align(Alignment.Center) + .padding(12.dp) + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/AccountScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/AccountScreen.kt index a2e27a9..c7a933a 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/AccountScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/AccountScreen.kt @@ -30,18 +30,18 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList -import com.owenlejeune.tvtime.ui.viewmodel.RecommendedMediaViewModel import com.owenlejeune.tvtime.extensions.unlessEmpty import com.owenlejeune.tvtime.ui.components.AccountIcon -import com.owenlejeune.tvtime.ui.components.PagingPosterGrid -import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem -import com.owenlejeune.tvtime.ui.navigation.ListFetchFun -import com.owenlejeune.tvtime.ui.navigation.AppNavItem import com.owenlejeune.tvtime.ui.components.MediaResultCard -import com.owenlejeune.tvtime.utils.types.MediaViewType +import com.owenlejeune.tvtime.ui.components.PagingPosterGrid import com.owenlejeune.tvtime.ui.components.ScrollableTabs +import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem +import com.owenlejeune.tvtime.ui.navigation.AppNavItem +import com.owenlejeune.tvtime.ui.navigation.ListFetchFun +import com.owenlejeune.tvtime.ui.viewmodel.RecommendedMediaViewModel import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.TmdbUtils +import com.owenlejeune.tvtime.utils.types.MediaViewType import kotlinx.coroutines.launch import kotlin.reflect.KClass diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/ListDetailScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/ListDetailScreen.kt index 59dce50..32bfa7b 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/ListDetailScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/ListDetailScreen.kt @@ -43,14 +43,14 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.model.* import com.owenlejeune.tvtime.extensions.WindowSizeClass import com.owenlejeune.tvtime.extensions.unlessEmpty import com.owenlejeune.tvtime.preferences.AppPreferences +import com.owenlejeune.tvtime.ui.components.RatingView import com.owenlejeune.tvtime.ui.components.Spinner import com.owenlejeune.tvtime.ui.components.SwitchPreference import com.owenlejeune.tvtime.ui.navigation.AppNavItem -import com.owenlejeune.tvtime.utils.types.MediaViewType -import com.owenlejeune.tvtime.ui.components.RatingView import com.owenlejeune.tvtime.ui.theme.* import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.TmdbUtils +import com.owenlejeune.tvtime.utils.types.MediaViewType import de.charlex.compose.RevealDirection import de.charlex.compose.RevealSwipe import kotlinx.coroutines.CoroutineScope diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt index adf6a9c..fd7b6b1 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt @@ -1,6 +1,8 @@ package com.owenlejeune.tvtime.ui.screens import android.content.Context +import android.content.Intent +import android.net.Uri import android.widget.Toast import androidx.compose.animation.* import androidx.compose.animation.core.LinearEasing @@ -8,6 +10,7 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -16,6 +19,7 @@ import androidx.compose.material.icons.filled.Movie import androidx.compose.material.icons.filled.Send import androidx.compose.material.icons.outlined.ExpandMore import androidx.compose.material3.* +import androidx.compose.material.TabRow import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -30,15 +34,19 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastForEachIndexed import androidx.navigation.NavController import coil.compose.AsyncImage +import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState +import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.owenlejeune.tvtime.R @@ -60,10 +68,13 @@ import com.owenlejeune.tvtime.ui.theme.FavoriteSelected import com.owenlejeune.tvtime.ui.theme.RatingSelected import com.owenlejeune.tvtime.ui.theme.WatchlistSelected import com.owenlejeune.tvtime.ui.theme.actionButtonColor +import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.TmdbUtils import kotlinx.coroutines.* +import okhttp3.internal.notify import org.json.JSONObject +import org.koin.androidx.compose.koinViewModel import org.koin.java.KoinJavaComponent.get import java.text.DecimalFormat @@ -200,7 +211,6 @@ private fun MediaViewContent( ) Column( -// modifier = Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { if (type == MediaViewType.MOVIE) { @@ -442,6 +452,8 @@ private fun MainContent( VideosCard(itemId = itemId, service = service, modifier = Modifier.fillMaxWidth()) AdditionalDetailsCard(itemId = itemId, mediaItem = mediaItem, service = service, type = type) + + WatchProvidersCard(itemId = itemId, service = service) if (windowSize != WindowSizeClass.Expanded) { ReviewsCard(itemId = itemId, service = service) @@ -966,7 +978,7 @@ private fun AdditionalDetailsCard( modifier = Modifier .fillMaxWidth() .wrapContentHeight() - .padding(vertical = 12.dp, horizontal = 16.dp), + .padding(vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { AdditionalDetailItem( @@ -1072,12 +1084,14 @@ private fun AdditionalDetailItem( Text( text = title, fontSize = 16.sp, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(horizontal = 16.dp) ) Text( text = subtext, fontSize = 14.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp) ) if (includeDivider) { Divider() @@ -1218,7 +1232,7 @@ fun VideosCard( Text( text = stringResource(id = R.string.videos_label), style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 8.dp, top = 8.dp), + modifier = Modifier.padding(start = 12.dp, top = 8.dp), color = MaterialTheme.colorScheme.onSurfaceVariant ) }, @@ -1250,7 +1264,7 @@ private fun VideoGroup(results: List