diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/SegmentControl.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/SegmentControl.kt new file mode 100644 index 0000000..429d993 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/SegmentControl.kt @@ -0,0 +1,152 @@ +package com.owenlejeune.tvtime.ui.components + +import android.annotation.SuppressLint +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.owenlejeune.tvtime.extensions.toDp + +@SuppressLint("AutoboxingStateValueProperty") +@Composable +fun PillSegmentedControl( + items: List, + itemLabel: (index: Int, item: T) -> String, + onItemSelected: (index: Int, item: T) -> Unit, + modifier: Modifier = Modifier, + defaultSelectedItemIndex: Int = 0, + cornerRadius: Int = 50, + colors: SegmentControlColors = SegmentControlDefaults.pillSegmentControlColors() +) { + val selectedIndex = remember { mutableIntStateOf(defaultSelectedItemIndex) } + + val parentBoxSize = remember { mutableStateOf(IntSize.Zero) } + val textViewSize = remember { mutableStateOf(IntSize.Zero) } + val maxTextViewSize = remember { mutableStateOf(IntSize.Zero) } + + val offsetAnimation by animateDpAsState(targetValue = (selectedIndex.value * textViewSize.value.width).toDp()) + + BoxWithConstraints( + modifier = modifier.then( + Modifier + .border( + width = 1.dp, + color = colors.borderColor(), + shape = RoundedCornerShape(cornerRadius) + ) + .onGloballyPositioned { + parentBoxSize.value = it.size + } + ) + ) { + Box( + modifier = Modifier + .offset( + x = offsetAnimation, + y = 0.dp + ) + .clip(RoundedCornerShape(cornerRadius)) + .width((parentBoxSize.value.width / items.size).toDp()) + .height(parentBoxSize.value.height.toDp()) + .background(color = colors.pillColor()) + ) + + Row( + modifier = Modifier + .fillMaxWidth(), + ) { + items.forEachIndexed { index, item -> + Text( + text = itemLabel(index, item), + color = if (selectedIndex.value == index) colors.selectedTextColor() else colors.textColor(), + textAlign = TextAlign.Center, + modifier = Modifier + .padding(vertical = 8.dp) + .onGloballyPositioned { + textViewSize.value = it.size + if (it.size.width > maxTextViewSize.value.width) { + maxTextViewSize.value = it.size + } + } + .clickable( + interactionSource = MutableInteractionSource(), + indication = null + ) { + selectedIndex.value = index + onItemSelected(index, item) + } + .weight(1f) + ) + } + } + } +} + +interface SegmentControlColors { + @Composable fun borderColor(): Color + @Composable fun pillColor(): Color + @Composable fun textColor(): Color + @Composable fun selectedTextColor(): Color +} + +object SegmentControlDefaults { + @Composable + fun pillSegmentControlColors( + borderColor: Color = MaterialTheme.colorScheme.primary, + pillColor: Color = MaterialTheme.colorScheme.primary, + textColor: Color = MaterialTheme.colorScheme.primary, + selectedTextColor: Color = MaterialTheme.colorScheme.background + ): SegmentControlColors { + return object : SegmentControlColors { + @Composable override fun borderColor() = borderColor + @Composable override fun pillColor() = pillColor + @Composable override fun textColor() = textColor + @Composable override fun selectedTextColor() = selectedTextColor + } + } +} + +@Preview +@Composable +fun PillSegmentControlPreview() { + val items = listOf("One", "Two", "Three", "Four") + + val colors = SegmentControlDefaults.pillSegmentControlColors(borderColor = Color.Red, pillColor = Color.Red, textColor = Color.Red, selectedTextColor = Color.White) + + Column( + verticalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.background(Color.White) + ) { + PillSegmentedControl(items = items.subList(0, 2), itemLabel = { _, t -> t }, onItemSelected = { _, _ -> }, colors = colors) + PillSegmentedControl(items = items.subList(0, 3), itemLabel = { _, t -> t }, onItemSelected = { _, _ -> }, colors = colors) + PillSegmentedControl(items = items, itemLabel = { _, t -> t }, onItemSelected = { _, _ -> }, colors = colors) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt index f798039..cbdba10 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Widgets.kt @@ -1116,72 +1116,4 @@ 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, - modifier: Modifier = Modifier, - selectedColor: Color = MaterialTheme.colorScheme.secondary, - unselectedColor: Color = MaterialTheme.colorScheme.surfaceVariant -) { - Box( - modifier = modifier.then( - Modifier - .clip(RoundedCornerShape(percent = 25)) - .border(width = 1.dp, color = selectedColor, shape = RoundedCornerShape(percent = 25)) - .background(color = if(selected) selectedColor else unselectedColor) - .clickable(onClick = onSelected) - ) - ) { - Text( - text = text, - color = if (selected) unselectedColor else selectedColor, - fontSize = 16.sp, - modifier = Modifier - .align(Alignment.Center) - .padding(12.dp) - ) - } } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/CastCrewListScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/CastCrewListScreen.kt index 12c08fc..6ee51be 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/CastCrewListScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/CastCrewListScreen.kt @@ -2,7 +2,6 @@ package com.owenlejeune.tvtime.ui.screens import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -31,14 +30,10 @@ import androidx.navigation.NavController import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CrewMember -import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCrew -import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCast import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieCastMember -import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCast import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCastMember -import com.owenlejeune.tvtime.extensions.getCalendarYear import com.owenlejeune.tvtime.ui.components.MediaResultCard -import com.owenlejeune.tvtime.ui.components.SelectableTextChip +import com.owenlejeune.tvtime.ui.components.PillSegmentedControl import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.types.MediaViewType @@ -99,29 +94,15 @@ fun CastCrewListScreen( verticalArrangement = Arrangement.spacedBy(12.dp) ) { item { - Row( - modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - SelectableTextChip( - selected = castSelected, - onSelected = { castSelected = true }, - text = stringResource(id = R.string.actor_label), - selectedColor = MaterialTheme.colorScheme.tertiary, - unselectedColor = MaterialTheme.colorScheme.background - ) - SelectableTextChip( - selected = !castSelected, - onSelected = { castSelected = false }, - text = stringResource(id = R.string.production_label), - selectedColor = MaterialTheme.colorScheme.tertiary, - unselectedColor = MaterialTheme.colorScheme.background - ) - } + val labels = listOf(stringResource(id = R.string.actor_label), stringResource(id = R.string.production_label)) + PillSegmentedControl( + items = labels, + itemLabel = { _, t -> t }, + onItemSelected = { i, _ -> castSelected = i == 0 }, + modifier = Modifier.padding(horizontal = 8.dp) + ) } - - items(items!!) { item -> val additionalDetails = emptyList().toMutableList() when (item) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/KnownForScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/KnownForScreen.kt index 3f37189..6a87eab 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/KnownForScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/KnownForScreen.kt @@ -36,7 +36,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvCast import com.owenlejeune.tvtime.extensions.bringToFront import com.owenlejeune.tvtime.extensions.getCalendarYear import com.owenlejeune.tvtime.ui.components.MediaResultCard -import com.owenlejeune.tvtime.ui.components.SelectableTextChip +import com.owenlejeune.tvtime.ui.components.PillSegmentedControl import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel import com.owenlejeune.tvtime.utils.TmdbUtils @@ -99,19 +99,11 @@ fun KnownForScreen( modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - SelectableTextChip( - selected = actorSelected, - onSelected = { actorSelected = true }, - text = stringResource(id = R.string.actor_label), - selectedColor = MaterialTheme.colorScheme.tertiary, - unselectedColor = MaterialTheme.colorScheme.background - ) - SelectableTextChip( - selected = !actorSelected, - onSelected = { actorSelected = false }, - text = stringResource(id = R.string.production_label), - selectedColor = MaterialTheme.colorScheme.tertiary, - unselectedColor = MaterialTheme.colorScheme.background + val labels = listOf(stringResource(id = R.string.actor_label), stringResource(id = R.string.production_label)) + PillSegmentedControl( + items = labels, + itemLabel = { _, t -> t }, + onItemSelected = { i, _ -> actorSelected = i == 0} ) } } diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt index 95322c0..70cab85 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaDetailScreen.kt @@ -1,5 +1,6 @@ package com.owenlejeune.tvtime.ui.screens +import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import android.widget.Toast @@ -48,7 +49,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -112,10 +112,10 @@ import com.owenlejeune.tvtime.ui.components.FullScreenThumbnailVideoPlayer import com.owenlejeune.tvtime.ui.components.HtmlText import com.owenlejeune.tvtime.ui.components.ImageGalleryOverlay import com.owenlejeune.tvtime.ui.components.ListContentCard +import com.owenlejeune.tvtime.ui.components.PillSegmentedControl import com.owenlejeune.tvtime.ui.components.PosterItem import com.owenlejeune.tvtime.ui.components.RoundedChip import com.owenlejeune.tvtime.ui.components.RoundedTextField -import com.owenlejeune.tvtime.ui.components.SelectableTextChip import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard import com.owenlejeune.tvtime.ui.navigation.AppNavItem import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel @@ -986,6 +986,7 @@ private fun VideoGroup(results: List