add reviews

This commit is contained in:
Owen LeJeune
2022-02-26 20:43:19 -05:00
parent 4f3f756d6b
commit 86bfa78590
20 changed files with 480 additions and 55 deletions

View File

@@ -64,6 +64,7 @@ dependencies {
implementation "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
implementation "com.google.accompanist:accompanist-pager:${Versions.compose_accompanist}"
implementation "com.google.accompanist:accompanist-flowlayout:${Versions.compose_accompanist}"
// implementation "com.google.accompanist:accompanist-insets:${Versions.compose_accompanist}"
implementation "androidx.navigation:navigation-compose:${Versions.compose_navigation}"
implementation "androidx.paging:paging-compose:${Versions.compose_paging}"
implementation "androidx.constraintlayout:constraintlayout-compose:${Versions.compose_constraint_layout}"
@@ -85,11 +86,13 @@ dependencies {
implementation "me.onebone:toolbar-compose:2.3.1"
implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:v2.1.0'
// implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
// implementation 'com.github.HaarigerHarald:android-youtubeExtractor:v2.1.0'
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:11.0.1'
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:chromecast-sender:0.26'
implementation "org.jetbrains:markdown:0.2.1"
testImplementation "junit:junit:${Versions.junit}"
androidTestImplementation "androidx.test.ext:junit:${Versions.androidx_junit}"
androidTestImplementation "androidx.test.espresso:espresso-core:${Versions.espresso_core}"

View File

@@ -16,7 +16,8 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.TVTime">
android:theme="@style/Theme.TVTime"
android:windowSoftInputMode="adjustResize">
<!-- android:screenOrientation="portrait">-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -21,6 +21,7 @@ class MainActivity : ComponentActivity() {
setContent {
AppKeyboardFocusManager()
val displayUnderStatusBar = remember { mutableStateOf(false) }
// WindowCompat.setDecorFitsSystemWindows(window, false)
// WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
// val statusBarColor = if (displayUnderStatusBar.value) {
// Color.Transparent

View File

@@ -15,4 +15,6 @@ interface DetailService {
suspend fun getVideos(id: Int): Response<VideoResponse>
suspend fun getReviews(id: Int): Response<ReviewResponse>
}

View File

@@ -38,4 +38,7 @@ interface MoviesApi {
@GET("movie/{id}/videos")
suspend fun getVideos(@Path("id") id: Int): Response<VideoResponse>
@GET("movie/{id}/reviews")
suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse>
}

View File

@@ -48,4 +48,8 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
return service.getVideos(id)
}
override suspend fun getReviews(id: Int): Response<ReviewResponse> {
return service.getReviews(id)
}
}

View File

@@ -38,4 +38,7 @@ interface TvApi {
@GET("tv/{id}/videos")
suspend fun getVideos(@Path("id") id: Int): Response<VideoResponse>
@GET("movie/{id}/reviews")
suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse>
}

View File

@@ -47,4 +47,9 @@ class TvService: KoinComponent, DetailService, HomePageService {
override suspend fun getVideos(id: Int): Response<VideoResponse> {
return service.getVideos(id)
}
override suspend fun getReviews(id: Int): Response<ReviewResponse> {
return service.getReviews(id)
}
}

View File

@@ -0,0 +1,10 @@
package com.owenlejeune.tvtime.api.tmdb.model
import com.google.gson.annotations.SerializedName
class AuthorDetails(
@SerializedName("name") val name: String,
@SerializedName("username") val username: String,
@SerializedName("avatar_path") val avatarPath: String?,
@SerializedName("rating") val rating: Int
)

View File

@@ -0,0 +1,12 @@
package com.owenlejeune.tvtime.api.tmdb.model
import com.google.gson.annotations.SerializedName
class Review(
@SerializedName("id") val id: String,
@SerializedName("author") val author: String,
@SerializedName("author_details") val authorDetails: AuthorDetails,
@SerializedName("content") val content: String,
@SerializedName("created_at") val createdAt: String,
@SerializedName("updated_at") val updatedAt: String
)

View File

@@ -0,0 +1,8 @@
package com.owenlejeune.tvtime.api.tmdb.model
import com.google.gson.annotations.SerializedName
class ReviewResponse(
@SerializedName("page") val page: Int,
@SerializedName("results") val results: List<Review>
)

View File

@@ -5,6 +5,9 @@ import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material3.MaterialTheme
@@ -92,6 +95,40 @@ fun ExpandableContentCard(
}
}
@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),
backgroundColor = backgroundColor,
elevation = 8.dp
) {
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()
}
}
}
@Composable
fun TwoLineImageTextCard(
title: String,

View File

@@ -5,10 +5,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -37,7 +34,7 @@ import com.owenlejeune.tvtime.extensions.dpToPx
import com.owenlejeune.tvtime.extensions.listItems
import com.owenlejeune.tvtime.utils.TmdbUtils
private val POSTER_WIDTH = 127.dp
private val POSTER_WIDTH = 120.dp
private val POSTER_HEIGHT = 190.dp
@OptIn(ExperimentalFoundationApi::class)
@@ -51,7 +48,8 @@ fun PosterGrid(
LazyVerticalGrid(
cells = GridCells.Adaptive(minSize = POSTER_WIDTH),
contentPadding = PaddingValues(8.dp)
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
listItems(mediaList.value) { item ->
PosterItem(

View File

@@ -1,15 +1,16 @@
package com.owenlejeune.tvtime.ui.components
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.widget.TextView
import android.widget.Toast
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
@@ -22,16 +23,22 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
@@ -48,12 +55,21 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.core.text.HtmlCompat
import coil.compose.rememberImagePainter
import coil.transform.CircleCropTransformation
import com.google.accompanist.flowlayout.FlowRow
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.model.AuthorDetails
import com.owenlejeune.tvtime.utils.TmdbUtils
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser
@Composable
fun TopLevelSwitch(
@@ -334,6 +350,7 @@ fun RatingRing(
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RoundedTextField(
value: String,
@@ -352,31 +369,49 @@ fun RoundedTextField(
enabled: Boolean = true,
readOnly: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default
keyboardActions: KeyboardActions = KeyboardActions.Default,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
) {
Surface(
modifier = modifier,
shape = RoundedCornerShape(50.dp),
color = backgroundColor
) {
Box(
modifier = Modifier.padding(horizontal = 12.dp),
contentAlignment = Alignment.CenterStart
Row(Modifier.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
if (value.isEmpty() && placeHolder.isNotEmpty()) {
Text(
text = placeHolder,
style = textStyle,
color = placeHolderTextColor
)
if (leadingIcon != null) {
leadingIcon()
}
Row(
verticalAlignment = Alignment.CenterVertically
Box(modifier = Modifier
.fillMaxHeight()
.weight(1f),
contentAlignment = Alignment.CenterStart
) {
if (value.isEmpty() && placeHolder.isNotEmpty()) {
Text(
text = placeHolder,
style = textStyle,
color = placeHolderTextColor
)
}
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val coroutineScope = rememberCoroutineScope()
BasicTextField(
modifier = Modifier
.weight(1f)
.focusRequester(focusRequester),
.focusRequester(focusRequester)
.fillMaxWidth()
.bringIntoViewRequester(bringIntoViewRequester)
.onFocusEvent {
if (it.isFocused) {
coroutineScope.launch {
delay(200)
bringIntoViewRequester.bringIntoView()
}
}
},
value = value,
onValueChange = onValueChange,
singleLine = singleLine,
@@ -386,9 +421,12 @@ fun RoundedTextField(
enabled = enabled,
readOnly = readOnly,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions
keyboardActions = keyboardActions,
)
}
if (trailingIcon != null) {
trailingIcon()
}
}
}
@@ -399,6 +437,28 @@ fun RoundedTextField(
}
}
@Preview(widthDp = 300, heightDp = 40)
@Composable
private fun RoundedEditTextPreview() {
RoundedTextField(
value = "this is my value",
onValueChange = {},
placeHolder = "this is my placeholder",
trailingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = ""
)
},
leadingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = ""
)
}
)
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun FullScreenThumbnailVideoPlayer(
@@ -450,4 +510,90 @@ fun FullScreenThumbnailVideoPlayer(
}
}
}
}
@Composable
fun HtmlText(text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, parseMarkdownFirst: Boolean = true) {
val htmlString = if (parseMarkdownFirst) {
val flavour = CommonMarkFlavourDescriptor()
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(text)
HtmlGenerator(text, parsedTree, flavour).generateHtml()
} else {
text
}
AndroidView(
modifier = modifier,
factory = { context -> TextView(context) },
update = { textView ->
textView.textSize = 14f
textView.text = HtmlCompat.fromHtml(htmlString, HtmlCompat.FROM_HTML_MODE_COMPACT)
textView.setTextColor(color.toArgb())
}
)
}
@Composable
fun CircleBackgroundColorImage(
size: Dp,
backgroundColor: Color,
image: ImageVector,
modifier: Modifier = Modifier,
imageHeight: Dp? = null,
imageAlignment: Alignment = Alignment.Center,
contentDescription: String? = null,
colorFilter: ColorFilter? = null
) {
Box(
modifier = modifier
.clip(CircleShape)
.size(size)
.background(color = backgroundColor)
) {
val mod = if (imageHeight != null) {
Modifier.align(imageAlignment).height(height = imageHeight)
} else {
Modifier.align(imageAlignment)
}
Image(
imageVector = image,
contentDescription = contentDescription,
modifier = mod,
colorFilter = colorFilter
)
}
}
@Composable
fun AvatarImage(
size: Dp,
author: AuthorDetails,
modifier: Modifier = Modifier
) {
if (author.avatarPath != null) {
Image(
modifier = modifier.size(size),
painter = rememberImagePainter(
data = TmdbUtils.getFullAvatarPath(author),
builder = {
transformations(CircleCropTransformation())
}
),
contentDescription = ""
)
} else {
Box(
modifier = Modifier
.clip(CircleShape)
.size(size)
.background(color = MaterialTheme.colorScheme.tertiary)
) {
Text(
modifier = Modifier.fillMaxSize().padding(top = size/5),
text = author.name[0].uppercase(),
color = MaterialTheme.colorScheme.onTertiary,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
}
}
}

View File

@@ -5,21 +5,21 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
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.constraintlayout.compose.ConstraintLayout
import androidx.navigation.NavController
import com.owenlejeune.tvtime.R
@@ -381,6 +381,8 @@ private fun ContentColumn(
SimilarContentCard(itemId = itemId, service = service, mediaType = mediaType, appNavController = appNavController)
VideosCard(itemId = itemId, service = service)
ReviewsCard(itemId = itemId, service = service)
}
}
@@ -640,6 +642,152 @@ private fun VideoGroup(results: List<Video>, type: Video.Type, title: String) {
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun ReviewsCard(
itemId: Int?,
service: DetailService,
modifier: Modifier = Modifier
) {
val reviewsResponse = remember { mutableStateOf<ReviewResponse?>(null) }
itemId?.let {
if (reviewsResponse.value == null) {
fetchReviews(itemId, service, reviewsResponse)
}
}
// > 0
val hasReviews = reviewsResponse.value?.results?.size?.let { it > 0 }
val m = if (hasReviews == true) {
modifier.height(400.dp)
} else {
modifier.height(200.dp)
}
LazyListContentCard(
modifier = m
.fillMaxWidth(),
header = {
Text(
text = "Reviews",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
},
footer = {
Row(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.padding(top = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
var reviewTextState by remember { mutableStateOf("") }
RoundedTextField(
modifier = Modifier
.height(50.dp)
.padding(top = 4.dp)
.weight(1f),
value = reviewTextState,
onValueChange = { reviewTextState = it },
placeHolder = "Add a review",
backgroundColor = MaterialTheme.colorScheme.secondary,
placeHolderTextColor = MaterialTheme.colorScheme.background,
textColor = MaterialTheme.colorScheme.onSecondary
)
CircleBackgroundColorImage(
size = 40.dp,
backgroundColor = MaterialTheme.colorScheme.tertiary,
image = Icons.Filled.Send,
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.surfaceVariant),
contentDescription = ""
)
}
}
) {
val reviews = reviewsResponse.value?.results ?: emptyList()
if (reviews.isNotEmpty()) {
items(reviews.size) { i ->
val review = reviews[i]
Row(
modifier = Modifier
.fillMaxWidth()
.padding(end = 16.dp),
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
AvatarImage(
size = 50.dp,
author = review.authorDetails
)
// todo - only show this for user's review
CircleBackgroundColorImage(
image = Icons.Filled.Delete,
size = 30.dp,
backgroundColor = MaterialTheme.colorScheme.error,
contentDescription = "",
imageHeight = 15.dp,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surfaceVariant)
)
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = review.author,
color = MaterialTheme.colorScheme.secondary,
fontWeight = FontWeight.Bold
)
HtmlText(
text = review.content,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
val createdAt = TmdbUtils.formatDate(review.createdAt)
val updatedAt = TmdbUtils.formatDate(review.updatedAt)
var timestamp = stringResource(id = R.string.created_at_label, createdAt)
if (updatedAt != createdAt) {
timestamp += "\n${stringResource(id = R.string.updated_at_label, updatedAt)}"
}
Text(
text = timestamp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 12.sp
)
}
}
Divider(
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(horizontal = 50.dp, vertical = 6.dp)
)
}
} else {
item {
Text(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 24.dp, vertical = 22.dp),
text = stringResource(R.string.no_reviews_label),
color = MaterialTheme.colorScheme.tertiary,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineMedium
)
}
}
}
}
private fun fetchMediaItem(id: Int, service: DetailService, mediaItem: MutableState<DetailedItem?>) {
CoroutineScope(Dispatchers.IO).launch {
val response = service.getById(id)
@@ -739,4 +887,15 @@ private fun fetchCredits(id: Int, credits: MutableState<PersonCreditsResponse?>)
}
}
}
}
private fun fetchReviews(id: Int, service: DetailService, reviewResponse: MutableState<ReviewResponse?>) {
CoroutineScope(Dispatchers.IO).launch {
val result = service.getReviews(id)
if (result.isSuccessful) {
withContext(Dispatchers.Main) {
reviewResponse.value = result.body()
}
}
}
}

View File

@@ -1,6 +1,7 @@
package com.owenlejeune.tvtime.ui.screens
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
@@ -12,6 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@@ -139,7 +141,14 @@ private fun SearchTopBar(
focusRequester = focusRequester,
value = textState,
onValueChange = { textState = it },
placeHolder = stringResource(id = R.string.search_placeholder, title.value)
placeHolder = stringResource(id = R.string.search_placeholder, title.value),
trailingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = stringResource(R.string.search_icon_content_descriptor),
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary)
)
}
)
}
}
@@ -164,7 +173,9 @@ private fun BottomNavBar(navController: NavController, appBarTitle: MutableState
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
NavigationBar {
NavigationBar(
containerColor = MaterialTheme.colorScheme.primaryContainer
) {
BottomNavItem.Items.forEach { item ->
NavigationBarItem(
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
@@ -179,8 +190,8 @@ private fun BottomNavBar(navController: NavController, appBarTitle: MutableState
},
colors = NavigationBarItemDefaults
.colors(
selectedIconColor = MaterialTheme.colorScheme.onPrimary,
indicatorColor = MaterialTheme.colorScheme.primary
selectedIconColor = MaterialTheme.colorScheme.secondary,
indicatorColor = MaterialTheme.colorScheme.onSecondary
)
)
}

View File

@@ -1,12 +1,8 @@
package com.owenlejeune.tvtime.utils
import android.content.Context
import android.util.SparseArray
import androidx.compose.ui.text.intl.Locale
import at.huber.youtubeExtractor.VideoMeta
import at.huber.youtubeExtractor.YouTubeExtractor
import at.huber.youtubeExtractor.YtFile
import com.owenlejeune.tvtime.api.tmdb.model.*
import java.text.SimpleDateFormat
object TmdbUtils {
@@ -48,6 +44,19 @@ object TmdbUtils {
return getFullPersonImagePath(person.profilePath)
}
fun getFullAvatarPath(path: String?): String? {
return path?.let {
if (path.contains("http")) {
return path.substring(startIndex = 1)
}
"https://www.themoviedb.org/t/p/w150_and_h150_face${path}"
}
}
fun getFullAvatarPath(author: AuthorDetails): String? {
return getFullAvatarPath(author.avatarPath)
}
fun getMovieReleaseYear(movie: DetailedMovie): String {
return movie.releaseDate.split("-")[0]
}
@@ -139,19 +148,18 @@ object TmdbUtils {
}
fun getFullVideoUrl(video: Video): String {
// object: YouTubeExtractor(context) {
// override fun onExtractionComplete(
// ytFiles: SparseArray<YtFile>?,
// videoMeta: VideoMeta?
// ) {
// if (ytFiles != null) {
// }
// }
// }
if (video.site == "YouTube") {
return "http://www.youtube.com/watch?v=${video.key}"
}
return ""
}
fun formatDate(inDate: String): String {
val orig = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", java.util.Locale.US)
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", java.util.Locale.US)
val date = orig.parse(inDate)//.replace("Z", "+0000"))
return formatter.format(date)
}
}

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@@ -25,6 +25,10 @@
<string name="search_placeholder">Search %1$s</string>
<string name="created_at_label">Created at: %1$s</string>
<string name="updated_at_label">Updated at: %1$s</string>
<string name="no_reviews_label">No reviews</string>
<!-- preferences -->
<string name="preference_heading_search">Search</string>
<string name="preferences_persistent_search_title">Persistent search bar</string>
@@ -40,4 +44,5 @@
<string name="video_type_behind_the_scenes">Behind the Scenes</string>
<string name="video_type_featureette">Featurettes</string>
<string name="content_description_back_button">Back</string>
<string name="search_icon_content_descriptor">Search Icon</string>
</resources>

View File

@@ -2,6 +2,5 @@
<resources>
<style name="Theme.TVTime" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@color/purple_700</item>
</style>
</resources>