mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-08 12:42:44 -05:00
add watch provider info
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,6 @@ interface DetailService {
|
||||
|
||||
suspend fun getKeywords(id: Int): Response<KeywordsResponse>
|
||||
|
||||
suspend fun getWatchProviders(id: Int): Response<WatchProviderResponse>
|
||||
|
||||
}
|
||||
@@ -55,4 +55,7 @@ interface MoviesApi {
|
||||
@Query("session_id") sessionId: String
|
||||
): Response<StatusResponse>
|
||||
|
||||
@GET("movie/{id}/watch/providers")
|
||||
suspend fun getWatchProviders(@Path("id") id: Int): Response<WatchProviderResponse>
|
||||
|
||||
}
|
||||
@@ -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<WatchProviderResponse> {
|
||||
return movieService.getWatchProviders(id)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,4 +58,7 @@ interface TvApi {
|
||||
@GET("tv/{id}/season/{season}")
|
||||
suspend fun getSeason(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<Season>
|
||||
|
||||
@GET("tv/{id}/watch/providers")
|
||||
suspend fun getWatchProviders(@Path("id") seriesId: Int): Response<WatchProviderResponse>
|
||||
|
||||
}
|
||||
@@ -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<WatchProviderResponse> {
|
||||
return service.getWatchProviders(id)
|
||||
}
|
||||
|
||||
suspend fun getSeason(seriesId: Int, seasonId: Int): Response<Season> {
|
||||
return service.getSeason(seriesId, seasonId)
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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<String, WatchProviders>
|
||||
)
|
||||
|
||||
data class WatchProviders(
|
||||
@SerializedName("link")
|
||||
val link: String,
|
||||
@SerializedName("flatrate")
|
||||
val flaterate: List<WatchProviderDetails>?,
|
||||
@SerializedName("rent")
|
||||
val rent: List<WatchProviderDetails>?,
|
||||
@SerializedName("buy")
|
||||
val buy: List<WatchProviderDetails>?
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.owenlejeune.tvtime.extensions
|
||||
|
||||
fun <T> List<T>.lastOr(provider: () -> T): T {
|
||||
return if (isNotEmpty()) {
|
||||
last()
|
||||
} else {
|
||||
provider()
|
||||
}
|
||||
}
|
||||
@@ -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<Float> = 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
|
||||
// )
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<List<TmdbItem>>) -> Unit = {},
|
||||
onClick: (Int) -> Unit = {}
|
||||
) {
|
||||
val mediaList = remember { mutableStateOf(emptyList<TmdbItem>()) }
|
||||
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<TmdbItem>?,
|
||||
@@ -148,34 +121,6 @@ fun PagingPeoplePosterGrid(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PeoplePosterGrid(
|
||||
fetchPeople: (MutableState<List<HomePagePerson>>) -> Unit = {},
|
||||
onClick: (Int) -> Unit = {}
|
||||
) {
|
||||
val peopleList = remember { mutableStateOf(emptyList<HomePagePerson>()) }
|
||||
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(
|
||||
|
||||
@@ -122,7 +122,7 @@ fun ScrollableTabs(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SmallTabIndicator(
|
||||
fun SmallTabIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = MaterialTheme.colorScheme.primary
|
||||
) {
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Video>, type: Video.Type, title: String) {
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 8.dp, top = 8.dp)
|
||||
modifier = Modifier.padding(start = 12.dp, top = 8.dp)
|
||||
)
|
||||
|
||||
val posterWidth = 120.dp
|
||||
@@ -1277,6 +1291,136 @@ private fun VideoGroup(results: List<Video>, type: Video.Type, title: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun WatchProvidersCard(
|
||||
itemId: Int?,
|
||||
service: DetailService,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val watchProviders = remember { mutableStateOf<WatchProviders?>(null) }
|
||||
itemId?.let {
|
||||
if (watchProviders.value == null) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = service.getWatchProviders(it)
|
||||
if (response.isSuccessful) {
|
||||
val results = response.body()?.results
|
||||
results?.get(Locale.current.region)?.let { watchProviders.value = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watchProviders.value?.let { providers ->
|
||||
if (providers.buy?.isNotEmpty() == true || providers.rent?.isNotEmpty() == true || providers.flaterate?.isNotEmpty() == true) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.watch_providers_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 12.dp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
var selectedIndex by remember { mutableStateOf(
|
||||
when {
|
||||
providers.flaterate != null -> 0
|
||||
providers.rent != null -> 1
|
||||
providers.buy != null -> 2
|
||||
else -> -1
|
||||
}
|
||||
) }
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
providers.flaterate?.let {
|
||||
SelectableTextChip(
|
||||
selected = selectedIndex == 0,
|
||||
onSelected = { selectedIndex = 0 },
|
||||
text = stringResource(R.string.streaming_label)
|
||||
)
|
||||
}
|
||||
providers.rent?.let {
|
||||
SelectableTextChip(
|
||||
selected = selectedIndex == 1,
|
||||
onSelected = { selectedIndex = 1 },
|
||||
text = stringResource(R.string.rent_label)
|
||||
)
|
||||
}
|
||||
providers.buy?.let {
|
||||
SelectableTextChip(
|
||||
selected = selectedIndex == 2,
|
||||
onSelected = { selectedIndex = 2 },
|
||||
text = stringResource(R.string.buy_label)
|
||||
)
|
||||
}
|
||||
}
|
||||
Crossfade(
|
||||
modifier = modifier.padding(top = 4.dp, bottom = 12.dp),
|
||||
targetState = selectedIndex
|
||||
) { index ->
|
||||
when (index) {
|
||||
0 -> WatchProviderContainer(watchProviders = providers.flaterate!!, link = providers.link)
|
||||
1 -> WatchProviderContainer(watchProviders = providers.rent!!, link = providers.link)
|
||||
2 -> WatchProviderContainer(watchProviders = providers.buy!!, link = providers.link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WatchProviderContainer(
|
||||
watchProviders: List<WatchProviderDetails>,
|
||||
link: String
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
mainAxisSpacing = 8.dp,
|
||||
crossAxisSpacing = 4.dp
|
||||
) {
|
||||
watchProviders
|
||||
.sortedBy { it.displayPriority }
|
||||
.forEach { item ->
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.clickable {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||
context.startActivity(intent)
|
||||
}
|
||||
) {
|
||||
AsyncImage(
|
||||
model = TmdbUtils.fullImagePath(item.logoPath),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
)
|
||||
Text(
|
||||
text = item.providerName,
|
||||
fontSize = 10.sp,
|
||||
modifier = Modifier.width(48.dp),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReviewsCard(
|
||||
itemId: Int?,
|
||||
|
||||
@@ -26,12 +26,12 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
|
||||
import com.owenlejeune.tvtime.ui.components.ContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
|
||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -105,7 +105,9 @@ fun PersonDetailScreen(
|
||||
}
|
||||
}
|
||||
|
||||
ContentCard(title = stringResource(R.string.known_for_label)) {
|
||||
ContentCard(
|
||||
title = stringResource(R.string.known_for_label)
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
@@ -27,8 +27,8 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.ui.components.MediaResultCard
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.owenlejeune.tvtime.ui.views.ItemMoveCallback
|
||||
import com.owenlejeune.tvtime.utils.ResourceUtils
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koin.java.KoinJavaComponent.get
|
||||
@@ -224,7 +225,7 @@ private fun TopLevelSettingsCard(
|
||||
|
||||
@Composable
|
||||
private fun SearchPreferences() {
|
||||
val settingsViewModel = viewModel<SettingsViewModel>()
|
||||
val settingsViewModel = koinViewModel<SettingsViewModel>()
|
||||
|
||||
SwitchPreference(
|
||||
titleText = stringResource(R.string.preferences_persistent_search_title),
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
package com.owenlejeune.tvtime.utils
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountDetails
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AuthorDetails
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedTv
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Episode
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Image
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieReleaseResults
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Person
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Status
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
object TmdbUtils {
|
||||
|
||||
private const val POSTER_BASE = "https://image.tmdb.org/t/p/original"
|
||||
private const val BACKDROP_BASE = "https://www.themoviedb.org/t/p/original"
|
||||
private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
|
||||
// private const val BACKDROP_BASE = "https://www.themoviedb.org/t/p/original"
|
||||
// private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
|
||||
private const val GRAVATAR_BASE = "https://www.gravatar.com/avatar/"
|
||||
private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face"
|
||||
private const val STILL_BASE = "https://www.themoviedb.org/t/p/w454_and_h254_bestv2/"
|
||||
// private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face"
|
||||
// private const val STILL_BASE = "https://www.themoviedb.org/t/p/w454_and_h254_bestv2/"
|
||||
private const val BACKDROP_BASE = POSTER_BASE
|
||||
private const val PERSON_BASE = POSTER_BASE
|
||||
private const val AVATAR_BASE = POSTER_BASE
|
||||
private const val STILL_BASE = POSTER_BASE
|
||||
|
||||
private const val DEF_REGION = "US"
|
||||
|
||||
fun fullImagePath(sourcePath: String) = POSTER_BASE.plus(sourcePath)
|
||||
|
||||
fun getFullPosterPath(posterPath: String?): String? {
|
||||
return posterPath?.let {
|
||||
if (posterPath.isEmpty()) null else "${POSTER_BASE}${posterPath}"
|
||||
@@ -203,7 +221,7 @@ object TmdbUtils {
|
||||
return ""
|
||||
}
|
||||
|
||||
fun formatRevenue(revenue: Int): String {
|
||||
fun formatRevenue(revenue: Long): String {
|
||||
val decFormat = "%.1f"
|
||||
val thousands = revenue.toFloat() / 1000f
|
||||
if (thousands > 1000) {
|
||||
|
||||
@@ -221,4 +221,8 @@
|
||||
<string name="app_info_label">App Info</string>
|
||||
<string name="changelog_label">Changelog</string>
|
||||
<string name="powered_by_tmdb">Powered by TMDB</string>
|
||||
<string name="watch_providers_title">Watch Providers</string>
|
||||
<string name="streaming_label">Streaming</string>
|
||||
<string name="rent_label">Rent</string>
|
||||
<string name="buy_label">Buy</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user