milan jurkulák před 2 roky
rodič
revize
2e601da7b3

+ 194 - 0
src/main/kotlin/eu/mjdev/desktop/components/gallery/Gallery.kt

@@ -0,0 +1,194 @@
+package eu.mjdev.desktop.components.gallery
+
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.components.image.ImageAny
+import kotlinx.coroutines.delay
+import eu.mjdev.desktop.components.card.PhotoCard
+import eu.mjdev.desktop.components.immersivelist.ImmersiveInnerList
+import eu.mjdev.desktop.components.immersivelist.ImmersiveList
+import eu.mjdev.desktop.extensions.Compose.rememberDerivedState
+import eu.mjdev.desktop.extensions.Compose.rememberFocusRequester
+import eu.mjdev.desktop.helpers.bitmap.Bitmap
+import java.io.File
+
+@Composable
+fun Gallery(
+    modifier: Modifier = Modifier,
+    list: List<Any?> = listOf(Unit),
+    index: Int = 0,
+    delayedHide: Long = 5000,
+    controlsState: MutableState<Boolean> = remember { mutableStateOf(true) },
+    currentItemIndex: MutableIntState = remember { mutableIntStateOf(index) },
+    focusRequester: FocusRequester = rememberFocusRequester(),
+    imageScaleType: MutableState<ContentScale> = rememberSaveable(
+        saver = contentScaleSaver
+    ) {
+        mutableStateOf(ContentScale.Crop)
+    },
+    listState: LazyListState = rememberLazyListState(),
+//    switchImageScale: () -> Unit = {
+//        imageScaleType.value = when (imageScaleType.value) {
+//            ContentScale.Fit -> ContentScale.Crop
+//            ContentScale.Crop -> ContentScale.Fit
+//            else -> ContentScale.Fit
+//        }
+//    },
+    customContentViewer: @Composable (
+        src: Any?,
+        type: Any?,
+        list: List<Any?>
+    ) -> Unit = { _, _, _ -> },
+) {
+    val initialized = remember(list, index) { mutableStateOf(false) }
+//    val nextItem: () -> Unit = {
+//        if (currentItemIndex.intValue < (list.size - 1)) {
+//            currentItemIndex.intValue += 1
+//        }
+//        controlsState.value = true
+//    }
+//    val prevItem: () -> Unit = {
+//        if (currentItemIndex.intValue > 0) {
+//            currentItemIndex.intValue -= 1
+//        }
+//        controlsState.value = true
+//    }
+    val itemState = rememberDerivedState(0, currentItemIndex.intValue) {
+        list[currentItemIndex.intValue]
+    }
+    val itemType = rememberItemType(itemState.value)
+    val imageSrc = rememberImageFromItem(itemState.value)
+    BoxWithControls(
+        modifier = modifier,
+        src = itemState.value,
+        controlsState = controlsState,
+        controls = { src, bckColor, state ->
+            Box(
+                modifier = Modifier.fillMaxSize()
+            ) {
+                ItemInfo(
+                    modifier = Modifier
+                        .align(Alignment.TopCenter)
+                        .fillMaxWidth(),
+                    src = src,
+                    visible = state.value,
+                    backgroundColor = bckColor
+                )
+            }
+        }
+    ) { _, bckColor, state ->
+        ImmersiveList(
+            modifier = Modifier.fillMaxSize(),
+            currentItemIndex = currentItemIndex,
+            listAlignment = Alignment.BottomStart,
+            background = { _, _ ->
+                ImageAny(
+                    src = imageSrc.value,
+                    contentDescription = null,
+                    contentScale = imageScaleType.value,
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .focusable()
+                        .focusRequester(focusRequester)
+//                        .swipeGestures(
+//                            onTap = { state.toggle() },
+//                            onDoubleTap = { switchImageScale() },
+//                            onSwipeLeft = { nextItem() },
+//                            onSwipeRight = { prevItem() },
+//                            onSwipeUp = { nextItem() },
+//                            onSwipeDown = { prevItem() },
+//                        )
+                )
+                customContentViewer(imageSrc.value, itemType, list)
+            }
+        ) {
+            ImmersiveInnerList(
+                modifier = Modifier.fillMaxWidth(),
+                visible = controlsState.value,
+                backgroundColor = bckColor
+            ) {
+                LazyRow(
+                    horizontalArrangement = Arrangement.spacedBy(16.dp),
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(16.dp),
+                    contentPadding = PaddingValues(16.dp),
+                    state = listState
+                ) {
+                    itemsIndexed(list) { index, item ->
+                        val fr = rememberFocusRequester()
+                        PhotoCard(
+                            modifier = Modifier.immersiveListItem(index),
+                            item = item,
+                            focusRequester = fr,
+                        )
+                        SideEffect {
+                            val isFocused = (currentItemIndex.intValue == index)
+                            if (isFocused) {
+                                try {
+                                    fr.requestFocus()
+                                } catch (e: Exception) {
+                                    e.printStackTrace()
+                                }
+                            }
+                        }
+                    }
+                }
+                Spacer(modifier = Modifier.height(32.dp))
+            }
+        }
+    }
+    LaunchedEffect(currentItemIndex.intValue, controlsState.value) {
+        if (!initialized.value) {
+            listState.scrollToItem(currentItemIndex.intValue)
+            initialized.value = true
+        } else {
+            if (controlsState.value && (delayedHide > 0)) {
+                delay(delayedHide)
+                controlsState.value = false
+            }
+        }
+    }
+}
+
+@Composable
+fun rememberItemType(item: Any?): Any? = remember(item) { item }
+
+@Composable
+fun rememberImageFromItem(image: Any?): State<Any?> = remember(image) {
+    derivedStateOf {
+        val color = image as? Color
+        val file = image as? File
+        val bitmap = image as? Bitmap
+        val string = image.toString()
+        // todo more types & gif
+        file ?: bitmap ?: color ?: string
+    }
+}
+
+val contentScaleSaver = Saver<MutableState<ContentScale>, String>(
+    save = { value -> value.value.toString() },
+    restore = { str ->
+        mutableStateOf(
+            when (str) {
+                "Crop" -> ContentScale.Crop
+                "Fit" -> ContentScale.Fit
+                else -> ContentScale.Crop
+            }
+        )
+    }
+)

+ 85 - 0
src/main/kotlin/eu/mjdev/desktop/components/gallery/ItemInfo.kt

@@ -0,0 +1,85 @@
+package eu.mjdev.desktop.components.gallery
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import eu.mjdev.desktop.components.text.AutoHideEmptyText
+import eu.mjdev.desktop.extensions.ColorUtils.createVerticalColorBrush
+import eu.mjdev.desktop.extensions.ColorUtils.invert
+import eu.mjdev.desktop.helpers.compose.Gravity
+
+// todo
+@Suppress("UNUSED_PARAMETER")
+@Composable
+fun ItemInfo(
+    modifier: Modifier = Modifier,
+    src: Any? = null,
+    visible: Boolean = true,
+    backgroundColor: Color = Color.Transparent,
+//    metadataRetriever: MetadataRetriever = rememberMetaDataRetriever(),
+    titleFromItem: () -> Any? = {
+//        (src as? ItemWithTitle<*>)?.title
+    },
+    dateFromItem: () -> Any? = {
+//        (src as? ItemWithDate)?.date ?: "-"
+    },
+    detailsFromItem: () -> Any? = {
+//        metadataRetriever.getInfo(src)
+    },
+) {
+    val bck = createVerticalColorBrush(backgroundColor, Gravity.TOP)
+    val textColor by remember(backgroundColor) {
+        derivedStateOf {
+            backgroundColor.invert().copy(alpha = 1.0f)
+        }
+    }
+    AnimatedVisibility(
+        modifier = modifier,
+        visible = visible,
+        enter = slideInVertically(),
+        exit = slideOutVertically(),
+    ) {
+        Box(
+            modifier = modifier.background(bck),
+            contentAlignment = Alignment.TopStart
+        ) {
+            Column(
+                modifier = Modifier.padding(16.dp, 0.dp, 0.dp, 16.dp)
+            ) {
+                Spacer(modifier = Modifier.height(16.dp))
+                AutoHideEmptyText(
+                    text = titleFromItem(),
+                    color = textColor,
+                    fontSize = 24.sp
+                )
+                AutoHideEmptyText(
+                    text = dateFromItem(),
+                    color = textColor,
+                    fontSize = 16.sp
+                )
+                AutoHideEmptyText(
+                    text = detailsFromItem(),
+                    color = textColor,
+                    fontSize = 12.sp,
+                    lineHeight = 12.sp
+                )
+                Spacer(modifier = Modifier.height(16.dp))
+            }
+        }
+    }
+}