All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.github.lyxnx.compose.pine.Rating.kt Maven / Gradle / Ivy

package io.github.lyxnx.compose.pine

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import compose.icons.TablerIcons
import compose.icons.tablericons.Filled
import compose.icons.tablericons.Outline
import compose.icons.tablericons.filled.Heart
import compose.icons.tablericons.filled.Star
import compose.icons.tablericons.outline.Heart
import compose.icons.tablericons.outline.Star
import io.github.lyxnx.compose.ui.drawWithLayer
import io.github.lyxnx.compose.ui.ifTrue

/**
 * Represents the rating bar type
 */
public enum class RatingType {
    Star, Heart
}

/**
 * A Pine Theme rating input component
 *
 * @param modifier modifier to apply the the rating display
 * @param rating the current rating to display. This can be fractional, for example, 4.5 and a [maxRating] of 5 will show 4 full stars and a half star being shown
 * @param ratingType type of rating to use
 * @param colors color to apply to the rating icons
 * @param spacing spacing between each rating icon
 * @param maxRating the maximum rating value
 * @param itemSize the size of each item
 * @param onRatingSelected action to perform when a rating item is selected. The received value corresponds to the
 * rating value, not the index of selected item
 */
@Composable
public fun RatingDisplay(
    modifier: Modifier = Modifier,
    rating: Float = 0f,
    ratingType: RatingType = RatingType.Star,
    colors: RatingColors = RatingDefaults.starColors(),
    spacing: Dp = RatingDefaults.ItemSpacing,
    maxRating: Int = RatingDefaults.MaxRating,
    itemSize: DpSize = RatingDefaults.ItemSize,
    onRatingSelected: ((Int) -> Unit)? = null
) {
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.spacedBy(spacing),
        verticalAlignment = Alignment.CenterVertically
    ) {
        repeat(maxRating) { i ->
            RatingDisplayItem(
                type = ratingType,
                colors = colors,
                onClick = {
                    onRatingSelected!!.invoke(i + 1)
                }.takeIf { onRatingSelected != null },
                fraction = (rating - i).coerceIn(0f, 1f),
                modifier = Modifier.size(itemSize)
            )
        }
    }
}

/**
 * A Pine Theme rating display item. This is used by a [RatingDisplay] but can also be used on its own
 *
 * @param type the type of rating item to display
 */
@Suppress("NAME_SHADOWING")
@Composable
public fun RatingDisplayItem(
    modifier: Modifier = Modifier,
    fraction: Float = 0f,
    type: RatingType = RatingType.Star,
    colors: RatingColors = RatingDefaults.starColors(),
    size: DpSize = RatingDefaults.ItemSize,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    onClick: (() -> Unit)? = null
) {
    val isPressed by interactionSource.collectIsPressedAsState()
    val color by colors.iconColor(isPressed)
    val fraction = remember(fraction) { fraction.coerceIn(0f, 1f) }

    val image =
        rememberVectorPainter(if (type == RatingType.Star) TablerIcons.Outline.Star else TablerIcons.Outline.Heart)
    val imageFilled =
        rememberVectorPainter(if (type == RatingType.Star) TablerIcons.Filled.Star else TablerIcons.Filled.Heart)

    Canvas(
        modifier
            .ifTrue(onClick != null) {
                clickable(
                    interactionSource = interactionSource,
                    onClick = onClick!!,
                    indication = LocalIndication.current
                )
            }
            .size(size)
    ) {
        val size = this.size

        with(image) {
            draw(
                size = Size(size.width, size.height),
                colorFilter = ColorFilter.tint(color)
            )
        }

        drawWithLayer {
            with(imageFilled) {
                draw(
                    size = Size(size.width, size.height),
                    colorFilter = ColorFilter.tint(color)
                )
            }

            drawRect(
                color = Color.Transparent,
                topLeft = Offset(size.width * fraction, 0f),
                // the fraction is how much of the star we want visible, so 1 - fraction is the width to cover up
                size = Size(size.width * (1f - fraction), size.height),
                blendMode = BlendMode.SrcIn
            )
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy