|
|
@@ -10,27 +10,32 @@ package org.mjdev.tvapp.base.ui.components.carousel
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
import android.content.res.Configuration
|
|
|
+import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
|
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
import androidx.compose.runtime.Composable
|
|
|
+import androidx.compose.runtime.LaunchedEffect
|
|
|
import androidx.compose.runtime.MutableState
|
|
|
-import androidx.compose.runtime.mutableIntStateOf
|
|
|
+import androidx.compose.runtime.mutableFloatStateOf
|
|
|
+import androidx.compose.runtime.mutableStateOf
|
|
|
import androidx.compose.runtime.remember
|
|
|
import androidx.compose.ui.Modifier
|
|
|
import androidx.compose.ui.focus.FocusState
|
|
|
import androidx.compose.ui.focus.onFocusChanged
|
|
|
+import androidx.compose.ui.input.pointer.pointerInput
|
|
|
import androidx.compose.ui.layout.ContentScale
|
|
|
import androidx.compose.ui.platform.LocalConfiguration
|
|
|
import androidx.compose.ui.tooling.preview.Preview
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
import androidx.tv.material3.CardScale
|
|
|
import androidx.tv.material3.Carousel
|
|
|
+import androidx.tv.material3.CarouselState
|
|
|
import androidx.tv.material3.ExperimentalTvMaterial3Api
|
|
|
import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
|
|
|
import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
|
|
|
+import kotlin.math.abs
|
|
|
|
|
|
-// todo swipe left and swipe right
|
|
|
@SuppressLint("AutoboxingStateValueProperty")
|
|
|
@OptIn(ExperimentalTvMaterial3Api::class)
|
|
|
@Preview
|
|
|
@@ -38,30 +43,40 @@ import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
|
|
|
fun BigCarousel(
|
|
|
modifier: Modifier = Modifier,
|
|
|
items: List<Any?> = listOf(Unit, Unit, Unit),
|
|
|
+ autoScrollDurationMillis: Long = 500,
|
|
|
+ focusState: MutableState<FocusState?> = rememberFocusState(items),
|
|
|
+ carouselState: MutableState<CarouselState> = remember { mutableStateOf(CarouselState()) },
|
|
|
onItemSelected: (movie: Any?) -> Unit = {},
|
|
|
onItemClicked: (movie: Any?) -> Unit = {},
|
|
|
) {
|
|
|
val height = LocalConfiguration.current.let { config ->
|
|
|
- if (config.orientation == Configuration.ORIENTATION_PORTRAIT) config.screenWidthDp * 0.4f
|
|
|
- else config.screenHeightDp * 0.4f
|
|
|
+ if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
|
|
|
+ config.screenWidthDp * 0.4f
|
|
|
+ else
|
|
|
+ config.screenHeightDp * 0.4f
|
|
|
}
|
|
|
- val focusState: MutableState<FocusState?> = rememberFocusState(items)
|
|
|
- val itemIndex = remember { mutableIntStateOf(0) }
|
|
|
- val selectedItem: () -> Any? = { items[itemIndex.value] }
|
|
|
+ val selectedItem: () -> Any? = { items[carouselState.value.activeItemIndex] }
|
|
|
+ val scrollDelta = remember { mutableFloatStateOf(0f) }
|
|
|
BoxWithConstraints {
|
|
|
Carousel(
|
|
|
+ autoScrollDurationMillis = autoScrollDurationMillis,
|
|
|
+ carouselState = carouselState.value,
|
|
|
itemCount = items.size,
|
|
|
modifier = modifier
|
|
|
.recomposeHighlighter()
|
|
|
.fillMaxWidth()
|
|
|
.height(height.dp),
|
|
|
) { indexOfCarouselItem ->
|
|
|
- itemIndex.value = indexOfCarouselItem
|
|
|
CarouselCard(
|
|
|
item = items[indexOfCarouselItem],
|
|
|
modifier = modifier
|
|
|
.fillMaxWidth()
|
|
|
.height(height.dp)
|
|
|
+ .pointerInput(Unit) {
|
|
|
+ detectHorizontalDragGestures { _, dragAmount ->
|
|
|
+ scrollDelta.value = dragAmount
|
|
|
+ }
|
|
|
+ }
|
|
|
.onFocusChanged { state ->
|
|
|
if (state.isFocused || state.hasFocus) {
|
|
|
onItemSelected(selectedItem())
|
|
|
@@ -76,4 +91,27 @@ fun BigCarousel(
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
+ // todo improve
|
|
|
+ LaunchedEffect(scrollDelta.value) {
|
|
|
+ val activeItemIndex = carouselState.value.activeItemIndex
|
|
|
+ carouselState.value.pauseAutoScroll(activeItemIndex)
|
|
|
+ if(abs(scrollDelta.value) > 250) {
|
|
|
+ if (scrollDelta.value < 0) {
|
|
|
+ val nextItem = carouselState.value.activeItemIndex + 1
|
|
|
+ if (nextItem <= items.size) {
|
|
|
+ carouselState.value = CarouselState(nextItem).apply {
|
|
|
+ pauseAutoScroll(nextItem)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (scrollDelta.value > 0) {
|
|
|
+ val prevItem = carouselState.value.activeItemIndex - 1
|
|
|
+ if (prevItem >= 0) {
|
|
|
+ carouselState.value = CarouselState(prevItem).apply {
|
|
|
+ pauseAutoScroll(prevItem)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|