mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-17 17:21:06 -05:00
add reviews
This commit is contained in:
@@ -64,6 +64,7 @@ dependencies {
|
|||||||
implementation "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
|
implementation "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
|
||||||
implementation "com.google.accompanist:accompanist-pager:${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-flowlayout:${Versions.compose_accompanist}"
|
||||||
|
// implementation "com.google.accompanist:accompanist-insets:${Versions.compose_accompanist}"
|
||||||
implementation "androidx.navigation:navigation-compose:${Versions.compose_navigation}"
|
implementation "androidx.navigation:navigation-compose:${Versions.compose_navigation}"
|
||||||
implementation "androidx.paging:paging-compose:${Versions.compose_paging}"
|
implementation "androidx.paging:paging-compose:${Versions.compose_paging}"
|
||||||
implementation "androidx.constraintlayout:constraintlayout-compose:${Versions.compose_constraint_layout}"
|
implementation "androidx.constraintlayout:constraintlayout-compose:${Versions.compose_constraint_layout}"
|
||||||
@@ -85,11 +86,13 @@ dependencies {
|
|||||||
|
|
||||||
implementation "me.onebone:toolbar-compose:2.3.1"
|
implementation "me.onebone:toolbar-compose:2.3.1"
|
||||||
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
|
// implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
|
||||||
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:v2.1.0'
|
// implementation 'com.github.HaarigerHarald:android-youtubeExtractor:v2.1.0'
|
||||||
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:11.0.1'
|
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:11.0.1'
|
||||||
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:chromecast-sender:0.26'
|
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:chromecast-sender:0.26'
|
||||||
|
|
||||||
|
implementation "org.jetbrains:markdown:0.2.1"
|
||||||
|
|
||||||
testImplementation "junit:junit:${Versions.junit}"
|
testImplementation "junit:junit:${Versions.junit}"
|
||||||
androidTestImplementation "androidx.test.ext:junit:${Versions.androidx_junit}"
|
androidTestImplementation "androidx.test.ext:junit:${Versions.androidx_junit}"
|
||||||
androidTestImplementation "androidx.test.espresso:espresso-core:${Versions.espresso_core}"
|
androidTestImplementation "androidx.test.espresso:espresso-core:${Versions.espresso_core}"
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.TVTime">
|
android:theme="@style/Theme.TVTime"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- android:screenOrientation="portrait">-->
|
<!-- android:screenOrientation="portrait">-->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
setContent {
|
setContent {
|
||||||
AppKeyboardFocusManager()
|
AppKeyboardFocusManager()
|
||||||
val displayUnderStatusBar = remember { mutableStateOf(false) }
|
val displayUnderStatusBar = remember { mutableStateOf(false) }
|
||||||
|
// WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
// WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
|
// WindowCompat.setDecorFitsSystemWindows(window, !displayUnderStatusBar.value)
|
||||||
// val statusBarColor = if (displayUnderStatusBar.value) {
|
// val statusBarColor = if (displayUnderStatusBar.value) {
|
||||||
// Color.Transparent
|
// Color.Transparent
|
||||||
|
|||||||
@@ -15,4 +15,6 @@ interface DetailService {
|
|||||||
|
|
||||||
suspend fun getVideos(id: Int): Response<VideoResponse>
|
suspend fun getVideos(id: Int): Response<VideoResponse>
|
||||||
|
|
||||||
|
suspend fun getReviews(id: Int): Response<ReviewResponse>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -38,4 +38,7 @@ interface MoviesApi {
|
|||||||
@GET("movie/{id}/videos")
|
@GET("movie/{id}/videos")
|
||||||
suspend fun getVideos(@Path("id") id: Int): Response<VideoResponse>
|
suspend fun getVideos(@Path("id") id: Int): Response<VideoResponse>
|
||||||
|
|
||||||
|
@GET("movie/{id}/reviews")
|
||||||
|
suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -48,4 +48,8 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
|||||||
return service.getVideos(id)
|
return service.getVideos(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getReviews(id: Int): Response<ReviewResponse> {
|
||||||
|
return service.getReviews(id)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -38,4 +38,7 @@ interface TvApi {
|
|||||||
@GET("tv/{id}/videos")
|
@GET("tv/{id}/videos")
|
||||||
suspend fun getVideos(@Path("id") id: Int): Response<VideoResponse>
|
suspend fun getVideos(@Path("id") id: Int): Response<VideoResponse>
|
||||||
|
|
||||||
|
@GET("movie/{id}/reviews")
|
||||||
|
suspend fun getReviews(@Path("id") id: Int): Response<ReviewResponse>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -47,4 +47,9 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
override suspend fun getVideos(id: Int): Response<VideoResponse> {
|
override suspend fun getVideos(id: Int): Response<VideoResponse> {
|
||||||
return service.getVideos(id)
|
return service.getVideos(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getReviews(id: Int): Response<ReviewResponse> {
|
||||||
|
return service.getReviews(id)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
@@ -5,6 +5,9 @@ import androidx.compose.animation.core.LinearOutSlowInEasing
|
|||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material3.MaterialTheme
|
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
|
@Composable
|
||||||
fun TwoLineImageTextCard(
|
fun TwoLineImageTextCard(
|
||||||
title: String,
|
title: String,
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.GridCells
|
import androidx.compose.foundation.lazy.GridCells
|
||||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.extensions.listItems
|
||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
|
|
||||||
private val POSTER_WIDTH = 127.dp
|
private val POSTER_WIDTH = 120.dp
|
||||||
private val POSTER_HEIGHT = 190.dp
|
private val POSTER_HEIGHT = 190.dp
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@@ -51,7 +48,8 @@ fun PosterGrid(
|
|||||||
|
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
cells = GridCells.Adaptive(minSize = POSTER_WIDTH),
|
cells = GridCells.Adaptive(minSize = POSTER_WIDTH),
|
||||||
contentPadding = PaddingValues(8.dp)
|
contentPadding = PaddingValues(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
listItems(mediaList.value) { item ->
|
listItems(mediaList.value) { item ->
|
||||||
PosterItem(
|
PosterItem(
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package com.owenlejeune.tvtime.ui.components
|
package com.owenlejeune.tvtime.ui.components
|
||||||
|
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.*
|
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.selection.toggleable
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
@@ -22,16 +23,22 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
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.CornerRadius
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
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.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.TextLayoutResult
|
import androidx.compose.ui.text.TextLayoutResult
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
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.viewinterop.AndroidView
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import coil.compose.rememberImagePainter
|
import coil.compose.rememberImagePainter
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.owenlejeune.tvtime.R
|
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.YouTubePlayer
|
||||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
|
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
|
||||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView
|
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
|
@Composable
|
||||||
fun TopLevelSwitch(
|
fun TopLevelSwitch(
|
||||||
@@ -334,6 +350,7 @@ fun RatingRing(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RoundedTextField(
|
fun RoundedTextField(
|
||||||
value: String,
|
value: String,
|
||||||
@@ -352,15 +369,25 @@ fun RoundedTextField(
|
|||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = RoundedCornerShape(50.dp),
|
shape = RoundedCornerShape(50.dp),
|
||||||
color = backgroundColor
|
color = backgroundColor
|
||||||
) {
|
) {
|
||||||
Box(
|
Row(Modifier.padding(horizontal = 12.dp),
|
||||||
modifier = Modifier.padding(horizontal = 12.dp),
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
if (leadingIcon != null) {
|
||||||
|
leadingIcon()
|
||||||
|
}
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(1f),
|
||||||
contentAlignment = Alignment.CenterStart
|
contentAlignment = Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
if (value.isEmpty() && placeHolder.isNotEmpty()) {
|
if (value.isEmpty() && placeHolder.isNotEmpty()) {
|
||||||
@@ -370,13 +397,21 @@ fun RoundedTextField(
|
|||||||
color = placeHolderTextColor
|
color = placeHolderTextColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
verticalAlignment = Alignment.CenterVertically
|
val coroutineScope = rememberCoroutineScope()
|
||||||
) {
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.focusRequester(focusRequester)
|
||||||
.focusRequester(focusRequester),
|
.fillMaxWidth()
|
||||||
|
.bringIntoViewRequester(bringIntoViewRequester)
|
||||||
|
.onFocusEvent {
|
||||||
|
if (it.isFocused) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
delay(200)
|
||||||
|
bringIntoViewRequester.bringIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
singleLine = singleLine,
|
singleLine = singleLine,
|
||||||
@@ -386,9 +421,12 @@ fun RoundedTextField(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
keyboardOptions = keyboardOptions,
|
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)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FullScreenThumbnailVideoPlayer(
|
fun FullScreenThumbnailVideoPlayer(
|
||||||
@@ -451,3 +511,89 @@ 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,21 +5,21 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material.icons.filled.Send
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.res.stringResource
|
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.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
@@ -381,6 +381,8 @@ private fun ContentColumn(
|
|||||||
SimilarContentCard(itemId = itemId, service = service, mediaType = mediaType, appNavController = appNavController)
|
SimilarContentCard(itemId = itemId, service = service, mediaType = mediaType, appNavController = appNavController)
|
||||||
|
|
||||||
VideosCard(itemId = itemId, service = service)
|
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?>) {
|
private fun fetchMediaItem(id: Int, service: DetailService, mediaItem: MutableState<DetailedItem?>) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val response = service.getById(id)
|
val response = service.getById(id)
|
||||||
@@ -740,3 +888,14 @@ 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens
|
package com.owenlejeune.tvtime.ui.screens
|
||||||
|
|
||||||
import androidx.compose.animation.rememberSplineBasedDecay
|
import androidx.compose.animation.rememberSplineBasedDecay
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -12,6 +13,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -139,7 +141,14 @@ private fun SearchTopBar(
|
|||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
value = textState,
|
value = textState,
|
||||||
onValueChange = { textState = it },
|
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 navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
|
||||||
NavigationBar {
|
NavigationBar(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||||
|
) {
|
||||||
BottomNavItem.Items.forEach { item ->
|
BottomNavItem.Items.forEach { item ->
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
|
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
|
||||||
@@ -179,8 +190,8 @@ private fun BottomNavBar(navController: NavController, appBarTitle: MutableState
|
|||||||
},
|
},
|
||||||
colors = NavigationBarItemDefaults
|
colors = NavigationBarItemDefaults
|
||||||
.colors(
|
.colors(
|
||||||
selectedIconColor = MaterialTheme.colorScheme.onPrimary,
|
selectedIconColor = MaterialTheme.colorScheme.secondary,
|
||||||
indicatorColor = MaterialTheme.colorScheme.primary
|
indicatorColor = MaterialTheme.colorScheme.onSecondary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
package com.owenlejeune.tvtime.utils
|
package com.owenlejeune.tvtime.utils
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.SparseArray
|
|
||||||
import androidx.compose.ui.text.intl.Locale
|
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 com.owenlejeune.tvtime.api.tmdb.model.*
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
object TmdbUtils {
|
object TmdbUtils {
|
||||||
|
|
||||||
@@ -48,6 +44,19 @@ object TmdbUtils {
|
|||||||
return getFullPersonImagePath(person.profilePath)
|
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 {
|
fun getMovieReleaseYear(movie: DetailedMovie): String {
|
||||||
return movie.releaseDate.split("-")[0]
|
return movie.releaseDate.split("-")[0]
|
||||||
}
|
}
|
||||||
@@ -139,19 +148,18 @@ object TmdbUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getFullVideoUrl(video: Video): String {
|
fun getFullVideoUrl(video: Video): String {
|
||||||
// object: YouTubeExtractor(context) {
|
|
||||||
// override fun onExtractionComplete(
|
|
||||||
// ytFiles: SparseArray<YtFile>?,
|
|
||||||
// videoMeta: VideoMeta?
|
|
||||||
// ) {
|
|
||||||
// if (ytFiles != null) {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (video.site == "YouTube") {
|
if (video.site == "YouTube") {
|
||||||
return "http://www.youtube.com/watch?v=${video.key}"
|
return "http://www.youtube.com/watch?v=${video.key}"
|
||||||
}
|
}
|
||||||
return ""
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
10
app/src/main/res/drawable/ic_search.xml
Normal file
10
app/src/main/res/drawable/ic_search.xml
Normal 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>
|
||||||
@@ -25,6 +25,10 @@
|
|||||||
|
|
||||||
<string name="search_placeholder">Search %1$s</string>
|
<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 -->
|
<!-- preferences -->
|
||||||
<string name="preference_heading_search">Search</string>
|
<string name="preference_heading_search">Search</string>
|
||||||
<string name="preferences_persistent_search_title">Persistent search bar</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_behind_the_scenes">Behind the Scenes</string>
|
||||||
<string name="video_type_featureette">Featurettes</string>
|
<string name="video_type_featureette">Featurettes</string>
|
||||||
<string name="content_description_back_button">Back</string>
|
<string name="content_description_back_button">Back</string>
|
||||||
|
<string name="search_icon_content_descriptor">Search Icon</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -2,6 +2,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.TVTime" parent="android:Theme.Material.Light.NoActionBar">
|
<style name="Theme.TVTime" parent="android:Theme.Material.Light.NoActionBar">
|
||||||
<item name="android:statusBarColor">@color/purple_700</item>
|
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user