mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-17 09:10:55 -05:00
create top level switch and palette view on settings page
This commit is contained in:
@@ -5,6 +5,8 @@ import android.widget.Toast
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.animation.rememberSplineBasedDecay
|
import androidx.compose.animation.rememberSplineBasedDecay
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
@@ -66,11 +68,13 @@ fun MyApp() {
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
SearchFab()
|
SearchFab()
|
||||||
}
|
}
|
||||||
) {
|
) { innerPadding ->
|
||||||
|
Box(modifier = Modifier.padding(innerPadding)) {
|
||||||
NavigationRoutes(navController = navController)
|
NavigationRoutes(navController = navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.MediaItem
|
||||||
|
|
||||||
|
object TmdbUtils {
|
||||||
|
|
||||||
|
fun getFullPosterPath(posterPath: String?): String {
|
||||||
|
return "https://image.tmdb.org/t/p/original${posterPath}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFullPosterPath(mediaItem: MediaItem): String {
|
||||||
|
return getFullPosterPath(mediaItem.posterPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,11 +3,12 @@ package com.owenlejeune.tvtime.extensions
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.lazy.LazyGridScope
|
import androidx.compose.foundation.lazy.LazyGridScope
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun <T: Any> LazyGridScope.items(
|
fun <T: Any> LazyGridScope.lazyPagingItems(
|
||||||
lazyPagingItems: LazyPagingItems<T>,
|
lazyPagingItems: LazyPagingItems<T>,
|
||||||
itemContent: @Composable LazyItemScope.(value: T?) -> Unit
|
itemContent: @Composable LazyItemScope.(value: T?) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -17,7 +18,16 @@ fun <T: Any> LazyGridScope.items(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun <T: Any> LazyGridScope.items(
|
fun <T: Any> LazyGridScope.listItems(
|
||||||
|
items: List<T>,
|
||||||
|
itemContent: @Composable (value: T) -> Unit
|
||||||
|
) {
|
||||||
|
items(items.size) { index ->
|
||||||
|
itemContent(items[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T: Any> LazyListScope.listItems(
|
||||||
items: List<T>,
|
items: List<T>,
|
||||||
itemContent: @Composable (value: T) -> Unit
|
itemContent: @Composable (value: T) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,27 +1,41 @@
|
|||||||
package com.owenlejeune.tvtime.ui.components
|
package com.owenlejeune.tvtime.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.foundation.*
|
||||||
import com.owenlejeune.tvtime.api.tmdb.model.MediaItem
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.rememberImagePainter
|
import coil.compose.rememberImagePainter
|
||||||
import coil.transform.RoundedCornersTransformation
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.TmdbUtils
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.model.MediaItem
|
||||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||||
import com.owenlejeune.tvtime.extensions.items
|
import com.owenlejeune.tvtime.extensions.listItems
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -33,7 +47,7 @@ fun PosterGrid(fetchMedia: (MutableState<List<MediaItem>>) -> Unit) {
|
|||||||
cells = GridCells.Fixed(count = 3),
|
cells = GridCells.Fixed(count = 3),
|
||||||
contentPadding = PaddingValues(8.dp)
|
contentPadding = PaddingValues(8.dp)
|
||||||
) {
|
) {
|
||||||
items(mediaList.value) { item ->
|
listItems(mediaList.value) { item ->
|
||||||
PosterItem(mediaItem = item)
|
PosterItem(mediaItem = item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +56,7 @@ fun PosterGrid(fetchMedia: (MutableState<List<MediaItem>>) -> Unit) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun PosterItem(mediaItem: MediaItem) {
|
fun PosterItem(mediaItem: MediaItem) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val poster = "https://image.tmdb.org/t/p/original${mediaItem.posterPath}"
|
val poster = TmdbUtils.getFullPosterPath(mediaItem)
|
||||||
Image(
|
Image(
|
||||||
painter = rememberImagePainter(
|
painter = rememberImagePainter(
|
||||||
data = poster,
|
data = poster,
|
||||||
@@ -62,3 +76,171 @@ fun PosterItem(mediaItem: MediaItem) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PosterItemPreview() {
|
||||||
|
PosterItem(mediaItem = object : MediaItem(
|
||||||
|
title = "Deadpool",
|
||||||
|
posterPath = "/fSRb7vyIP8rQpL0I47P3qUsEKX3.jpg"
|
||||||
|
){})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TopLevelSwitch(
|
||||||
|
text: String,
|
||||||
|
checkedState: MutableState<Boolean> = remember { mutableStateOf(false) },
|
||||||
|
onCheckChanged: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(100.dp)
|
||||||
|
.padding(12.dp),
|
||||||
|
shape = RoundedCornerShape(30.dp),
|
||||||
|
backgroundColor = when {
|
||||||
|
isSystemInDarkTheme() && checkedState.value -> MaterialTheme.colorScheme.primary
|
||||||
|
isSystemInDarkTheme() && !checkedState.value -> MaterialTheme.colorScheme.secondary
|
||||||
|
checkedState.value -> MaterialTheme.colorScheme.primaryContainer
|
||||||
|
else -> MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = when {
|
||||||
|
isSystemInDarkTheme() && checkedState.value -> MaterialTheme.colorScheme.onPrimary
|
||||||
|
isSystemInDarkTheme() && !checkedState.value -> MaterialTheme.colorScheme.onSecondary
|
||||||
|
checkedState.value -> MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
else -> MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(30.dp, 12.dp),
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
CustomSwitch(
|
||||||
|
modifier = Modifier.padding(40.dp, 12.dp),
|
||||||
|
onCheckChanged = { isChecked ->
|
||||||
|
checkedState.value = isChecked
|
||||||
|
onCheckChanged(isChecked)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CustomSwitch(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
switchState: MutableState<Boolean> = remember { mutableStateOf(false) },
|
||||||
|
onCheckChanged: (Boolean) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val width = 30.dp
|
||||||
|
val height = 15.dp
|
||||||
|
val gapBetweenThumbAndTrackEdge = 2.dp
|
||||||
|
|
||||||
|
val thumbRadius = (height / 2) - gapBetweenThumbAndTrackEdge
|
||||||
|
val animatePosition = animateFloatAsState(
|
||||||
|
targetValue = if (switchState.value) {
|
||||||
|
with(LocalDensity.current) { (width - thumbRadius - gapBetweenThumbAndTrackEdge).toPx() }
|
||||||
|
} else {
|
||||||
|
with (LocalDensity.current) { (thumbRadius + gapBetweenThumbAndTrackEdge).toPx() }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val uncheckedTrackColor = if (isSystemInDarkTheme()) MaterialTheme.colorScheme.surfaceVariant else MaterialTheme.colorScheme.outline
|
||||||
|
val uncheckedThumbColor = if (isSystemInDarkTheme()) MaterialTheme.colorScheme.outline else MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
val checkedTrackColor = if (isSystemInDarkTheme()) MaterialTheme.colorScheme.outline else MaterialTheme.colorScheme.primary
|
||||||
|
val checkedThumbColor = if (isSystemInDarkTheme()) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.primaryContainer
|
||||||
|
|
||||||
|
Canvas(
|
||||||
|
modifier = modifier
|
||||||
|
.size(width = width, height = height)
|
||||||
|
.scale(scale = 2f)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onTap = {
|
||||||
|
switchState.value = !switchState.value
|
||||||
|
onCheckChanged(switchState.value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
drawRoundRect(
|
||||||
|
color = if (switchState.value) checkedTrackColor else uncheckedTrackColor,
|
||||||
|
cornerRadius = CornerRadius(x = 10.dp.toPx(), y = 10.dp.toPx())
|
||||||
|
)
|
||||||
|
drawCircle(
|
||||||
|
color = if (switchState.value) checkedThumbColor else uncheckedThumbColor,
|
||||||
|
radius = thumbRadius.toPx(),
|
||||||
|
center = Offset(
|
||||||
|
x = animatePosition.value,
|
||||||
|
y = size.height / 2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "TopLevelSwitch", showBackground = true)
|
||||||
|
@Preview(name = "Dark TopLevelSwitch", showBackground = true, uiMode = UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
fun TopLevelSwitchPreview() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
TopLevelSwitch("This is a switch") { isChecked ->
|
||||||
|
Toast.makeText(context, "Switch changed to $isChecked", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PaletteView() {
|
||||||
|
class PaletteItem(val color: Color, val textColor: Color, val text: String)
|
||||||
|
|
||||||
|
val items = listOf(
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.onPrimary, "primary"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.primaryContainer, MaterialTheme.colorScheme.onPrimaryContainer, "primary container"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.inversePrimary, Color.Black, "inverse primary"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.secondary, MaterialTheme.colorScheme.onSecondary, "secondary"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.secondaryContainer, MaterialTheme.colorScheme.onSecondaryContainer, "secondary container"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.tertiary, MaterialTheme.colorScheme.onTertiary, "tertiary"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.tertiaryContainer, MaterialTheme.colorScheme.onTertiaryContainer, "tertiary container"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.background, MaterialTheme.colorScheme.onBackground, "background"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.surface, MaterialTheme.colorScheme.onSurface, "surface"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.surfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant, "surface variant"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.inverseSurface, MaterialTheme.colorScheme.inverseOnSurface, "inverse surface"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.error, MaterialTheme.colorScheme.onError, "error"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.errorContainer, MaterialTheme.colorScheme.onErrorContainer, "primary"),
|
||||||
|
PaletteItem(MaterialTheme.colorScheme.outline, Color.Black, "outline")
|
||||||
|
)
|
||||||
|
|
||||||
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
|
listItems(items) { item ->
|
||||||
|
PaletteItemView(
|
||||||
|
surfaceColor = item.color,
|
||||||
|
textColor = item.textColor,
|
||||||
|
text = item.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PaletteItemView(surfaceColor: Color, textColor: Color, text: String) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(100.dp)
|
||||||
|
.padding(12.dp),
|
||||||
|
shape = RoundedCornerShape(30.dp),
|
||||||
|
backgroundColor = surfaceColor
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = textColor,
|
||||||
|
modifier = Modifier.padding(30.dp, 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,23 +2,25 @@ package com.owenlejeune.tvtime.ui.screens
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.owenlejeune.tvtime.ui.components.PaletteView
|
||||||
|
import com.owenlejeune.tvtime.ui.components.TopLevelSwitch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsTab() {
|
fun SettingsTab() {
|
||||||
Column(
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
modifier = Modifier
|
val showPalette = remember { mutableStateOf(false) }
|
||||||
.fillMaxSize()
|
TopLevelSwitch(
|
||||||
.wrapContentSize(Alignment.Center)
|
text = "Show Palette",
|
||||||
) {
|
onCheckChanged = { isChecked ->
|
||||||
Text(
|
showPalette.value = isChecked
|
||||||
text = "Settings Tab",
|
}
|
||||||
color = MaterialTheme.colorScheme.onBackground
|
|
||||||
)
|
)
|
||||||
|
if (showPalette.value) {
|
||||||
|
PaletteView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user