diff --git a/app/src/main/java/org/fknives/android/compose/learning/pickers/TextPickerScreen.kt b/app/src/main/java/org/fknives/android/compose/learning/pickers/TextPickerScreen.kt index 8db189a..7725d97 100644 --- a/app/src/main/java/org/fknives/android/compose/learning/pickers/TextPickerScreen.kt +++ b/app/src/main/java/org/fknives/android/compose/learning/pickers/TextPickerScreen.kt @@ -59,9 +59,9 @@ fun TextPickerScreen() { var selected by remember { mutableStateOf(0) } LinedTextPicker( modifier = Modifier.defaultMinSize(minWidth = 200.dp), - selected = selected, + selectedIndex = selected, textForIndex = text::get, - onSelectedChange = { + onSelectedIndexChange = { selected = it }, textStyle = MaterialTheme.typography.h5, diff --git a/picker/src/main/java/org/fknives/android/compose/picker/number/NumberPickerScopeExtension.kt b/picker/src/main/java/org/fknives/android/compose/picker/number/NumberPickerScopeExtension.kt index 8106e43..ba9a0b0 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/number/NumberPickerScopeExtension.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/number/NumberPickerScopeExtension.kt @@ -46,8 +46,8 @@ fun NumberPickerScope.LinedInnerTextPicker(modifier: Modifier = Modifier) { modifier = modifier, textForIndex = textForIndex, itemCount = itemCount, - selected = selectedIndex, - onSelectedChange = onSelectedIndexChange, + selectedIndex = selectedIndex, + onSelectedIndexChange = onSelectedIndexChange, onIndexDifferenceChanging = onIndexDifferenceChanging, state = state ) diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/LinedTextPicker.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/LinedTextPicker.kt index 06bbaa7..d201a1d 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/LinedTextPicker.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/LinedTextPicker.kt @@ -11,17 +11,45 @@ import org.fknives.android.compose.picker.text.util.TextPickerDefaults import org.fknives.android.compose.picker.text.util.rememberTextPickerAnimator import org.fknives.android.compose.picker.text.util.rememberTextPickerState +/** + * Customised version of TextPicker. + * Adds additional Dividers by [textPickerContent] and places them by customized [textPickerMeasurePolicy] + * The Default Dividers can be customised by giving parameters to [LinedTextPickerContent] as [textPickerContent]. + * + * If no selection changes, snaps back to the selected element. + * Longer drags and letting go, animates scrolling over elements. + * Remembers the previously selected element and when selecting programmatically, scrolls to it.. + * (Selected by UI action makes it previously selected before sending back the event, aka no double animation) + * + * @param modifier Standard Modifier for the Layout + * @param textForIndex Used to display Text for the given index. Indexes are between `0 until [itemCount]`. + * @param itemCount Number of Elements that can be selected + * @param selectedIndex Currently selected index. Must be between `0 until [itemCount]` + * @param onSelectedIndexChange Notified after animation, what the User selected through dragging of the UI. + * @param textStyle Default textStyle provided for the Text Composables inside the Layout. + * @param roundAround Behavioural change: + * Setting this to True, means the elements behave like a wheel, and the last element is above the first. + * Setting this to False, means the first element has no elements above it, and the last element has no element below it. + * @param onIndexDifferenceChanging Signals the animation changes, how much the current dragging is away from [selectedIndex]. + * Negative values mean the index were decreased, Positive means it was increased. + * @param state Animation State for the TextPicker + * @param animator Uses state to Animate the Composable elements. Handles continous drag, calculating fling and snapping to an index. + * @param textPickerContent The actual Composables inside the TextPicker, by default it is the 4 Texts and the 2 Dividers + * @param textPickerMeasurePolicy The MeasurePolicy of the Layout, by default Translates the 4 Texts in [textPickerContent] + * according to [state] changed by [animator] + * @param pickerItem The Expected Text Composables in [textPickerContent]. + */ @Composable fun LinedTextPicker( modifier: Modifier = Modifier, textForIndex: (Int) -> String, itemCount: Int, - selected: Int, - onSelectedChange: (Int) -> Unit, + selectedIndex: Int, + onSelectedIndexChange: (Int) -> Unit, textStyle: TextStyle = LocalTextStyle.current, roundAround: Boolean = TextPickerDefaults.roundAround, onIndexDifferenceChanging: (Int) -> Unit = TextPickerDefaults.onIndexDifferenceChanging, - state: TextPickerState = rememberTextPickerState(selected = selected, itemCount = itemCount), + state: TextPickerState = rememberTextPickerState(selected = selectedIndex, itemCount = itemCount), animator: TextPickerAnimator = rememberTextPickerAnimator(roundAround = roundAround, onIndexDifferenceChanging = onIndexDifferenceChanging), textPickerContent: TextPickerContent = LinedTextPickerContent(), textPickerMeasurePolicy: MeasurePolicy = remember(state) { LinedTextPickerMeasurePolicy(state) }, @@ -31,8 +59,8 @@ fun LinedTextPicker( modifier = modifier, textForIndex = textForIndex, itemCount = itemCount, - selectedIndex = selected, - onSelectedIndexChange = onSelectedChange, + selectedIndex = selectedIndex, + onSelectedIndexChange = onSelectedIndexChange, textStyle = textStyle, roundAround = roundAround, onIndexDifferenceChanging = onIndexDifferenceChanging, diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/TextPicker.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/TextPicker.kt index fbd692f..f00887f 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/TextPicker.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/TextPicker.kt @@ -15,13 +15,48 @@ import androidx.compose.ui.layout.MeasurePolicy import androidx.compose.ui.text.TextStyle import org.fknives.android.compose.picker.text.content.DefaultTextPickerContent import org.fknives.android.compose.picker.text.content.DefaultTextPickerMeasurePolicy +import org.fknives.android.compose.picker.text.content.TextPickerContent import org.fknives.android.compose.picker.text.content.defaultNumberPickerTextAlphaModifier import org.fknives.android.compose.picker.text.util.TextPickerDefaults import org.fknives.android.compose.picker.text.util.rememberDefaultMoveUnsafeToProperIndex import org.fknives.android.compose.picker.text.util.rememberTextPickerAnimator import org.fknives.android.compose.picker.text.util.rememberTextPickerState -import org.fknives.android.compose.picker.text.util.rememberWrappedTextForIndex +import org.fknives.android.compose.picker.text.util.rememberSafeTextForIndex +/** + * NumberPicker Custom Layout Composable which shows given text on each index. + * Shows 3 selections options (while animating may show 4). + * Scrolling the Composable moves between elements and at the end of the movement is selects the most middle element. + * + * If no selection changes, snaps back to the selected element. + * Longer drags and letting go, animates scrolling over elements. + * Remembers the previously selected element and when selecting programmatically, scrolls to it.. + * (Selected by UI action makes it previously selected before sending back the event, aka no double animation) + * + * @param modifier Standard Modifier for the Layout + * @param textForIndex Used to display Text for the given index. Indexes are between `0 until [itemCount]`. + * @param itemCount Number of Elements that can be selected + * @param selectedIndex Currently selected index. Must be between `0 until [itemCount]` + * @param onSelectedIndexChange Notified after animation, what the User selected through dragging of the UI. + * @param textStyle Default textStyle provided for the Text Composables inside the Layout. + * @param roundAround Behavioural change: + * Setting this to True, means the elements behave like a wheel, and the last element is above the first. + * Setting this to False, means the first element has no elements above it, and the last element has no element below it. + * @param onIndexDifferenceChanging Signals the animation changes, how much the current dragging is away from [selectedIndex]. + * Negative values mean the index were decreased, Positive means it was increased. + * @param state Animation State for the TextPicker + * @param animator Uses state to Animate the Composable elements. Handles continous drag, calculating fling and snapping to an index. + * @param textPickerContent The actual Composables inside the TextPicker, by default it is the 4 Texts + * @param textPickerMeasurePolicy The MeasurePolicy of the Layout, by default Translates the 4 Texts in [textPickerContent] + * according to [state] changed by [animator] + * @param pickerItem The Expected Text Composables in [textPickerContent]. + * text is the given Text for the Item. + * translation is the movement of the element: + * - 1.0 -> selected + * - 0.5 -> unselected + * - 0.0 -> completely outside of boundaries. + * - movement between translation are expected to be proper (such as setting alpha based on the translation linearly). + */ @Composable fun TextPicker( modifier: Modifier = Modifier, @@ -40,7 +75,12 @@ fun TextPicker( ) { val moveUnsafeToProperIndex: (Int) -> Int = rememberDefaultMoveUnsafeToProperIndex(itemCount = itemCount, roundAround = roundAround) - val rememberTextForIndex = rememberWrappedTextForIndex(itemCount = itemCount, roundAround = roundAround, textForIndex = textForIndex) + val rememberTextForIndex = rememberSafeTextForIndex( + itemCount = itemCount, + roundAround = roundAround, + textForIndex = textForIndex, + moveUnsafeToProperIndex = moveUnsafeToProperIndex + ) Layout( modifier = modifier @@ -70,7 +110,6 @@ fun TextPicker( textPickerContent.Content( textForIndex = rememberTextForIndex, item = pickerItem, - moveUnsafeToProperIndex = moveUnsafeToProperIndex, selected = moveUnsafeToProperIndex(selectedIndex + state.indexOffset), before1TranslatePercent = state.before1TranslatePercent, itemTranslatePercent = state.itemTranslatePercent, @@ -83,16 +122,3 @@ fun TextPicker( ) } -fun interface TextPickerContent { - @Composable - fun Content( - textForIndex: (Int) -> String, - selected: Int, - moveUnsafeToProperIndex: (Int) -> Int, - before1TranslatePercent: Float, - itemTranslatePercent: Float, - after1TranslatePercent: Float, - after2TranslatePercent: Float, - item: @Composable (text: String, translation: Float) -> Unit - ) -} diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerAnimator.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerAnimator.kt index 72f7ada..3898565 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerAnimator.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerAnimator.kt @@ -16,6 +16,21 @@ import org.fknives.android.compose.picker.text.util.TextPickerDefaults import kotlin.math.abs import kotlin.math.roundToInt +/** + * Animator for [TextPicker] + * + * Handles 3 actions: + * - Continuous Dragging via [onDeltaY] + * - Scrolling Fling Animation via [flingAndSnap] + * - Scrolling newly selected item via [snapToIndex] + * + * @param flingDecaySpec AnimationSpec calculating expected scrolling by the given velocity. Used for the Fling Animation. + * @param flingAnimationSpec AnimationSpec used to do the actual scrolling animation, used in both animations. + * @param velocityMultiplier Controls Velocity to Slow or Fasten fling calculation, Raw Velocity is multiplied with this parameter. + * @param offsetLimiter Limits the offset animation. This can be used, to not let over scrolling happen after a given index or similar. + * @param onIndexDifferenceChanging Index Difference updates as animations and dragging happens. + * Negative values mean the index were decreased, Positive means it was increased. + */ class TextPickerAnimator( private val flingDecaySpec: DecayAnimationSpec, private val flingAnimationSpec: AnimationSpec = spring(0.75f), diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerState.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerState.kt index b1d407c..3cbc0af 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerState.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/TextPickerState.kt @@ -6,6 +6,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +/** + * Internal State for [TextPicker] + * + * Holds the Scrolling [offset] changes and Text [itemSize]-s. + * From this the Translations and index changes are Derived. + */ class TextPickerState( itemSizeState: MutableState, previousSelectedState: MutableState, diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerContent.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerContent.kt index 995f92f..efd47f8 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerContent.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerContent.kt @@ -1,24 +1,33 @@ package org.fknives.android.compose.picker.text.content import androidx.compose.runtime.Composable -import org.fknives.android.compose.picker.text.TextPickerContent +/** + * Default [TextPickerContent] for [TextPicker][org.fknives.android.compose.picker.text.TextPicker]. + */ class DefaultTextPickerContent : TextPickerContent { + /** + * Creates the actual content. + * + * Calculates the indexes around [selected], gets each item's text via [textForIndex]. + * From that the [item]s are placed with their respective text and given translation. + * + * The items are expected to be places by [DefaultTextPickerContent] + */ @Composable override fun Content( textForIndex: (Int) -> String, selected: Int, - moveUnsafeToProperIndex: (Int) -> Int, before1TranslatePercent: Float, itemTranslatePercent: Float, after1TranslatePercent: Float, after2TranslatePercent: Float, item: @Composable (text: String, translation: Float) -> Unit ) { - val before1 = moveUnsafeToProperIndex(selected - 1) - val after1 = moveUnsafeToProperIndex(selected + 1) - val after2 = moveUnsafeToProperIndex(selected + 2) + val before1 = selected - 1 + val after1 = selected + 1 + val after2 = selected + 2 item( text = textForIndex(before1), translation = before1TranslatePercent diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerLabel.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerLabel.kt index 2a524f6..f3261f0 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerLabel.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerLabel.kt @@ -7,6 +7,9 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.text.style.TextAlign import org.fknives.android.compose.picker.text.util.TextPickerDefaults +/** + * Default Text Label inside a [TextPicker][org.fknives.android.compose.picker.text.TextPicker] + */ @Composable fun DefaultNumberPickerText( text: String, @@ -19,6 +22,28 @@ fun DefaultNumberPickerText( ) } +/** + * Wraps [numberPickerLabel], to calculate the corresponding alpha from the given Translation. + * + * @param unselectedAlpha The alpha value an unselected element in [TextPicker][org.fknives.android.compose.picker.text.TextPicker] should have. + * @param selectedAlpha The alpha value a selected element in [TextPicker][org.fknives.android.compose.picker.text.TextPicker] should have. + * @param numberPickerLabel the actual label Composable. + * @param modifier additional [Modifier] for [numberPickerLabel] + * + * As described in [TextPicker][org.fknives.android.compose.picker.text.TextPicker]. + * translation is the movement of the element: + * - 1 -> selected + * - 0.5 -> unselected + * - 0 -> completely outside of boundaries. + * - movement between translation are expected to be proper (such as setting alpha based on the translation linearly). + * + * This mediator, calculates the linear alpha in the following way: + * - 1.0 -> [selectedAlpha] is shown + * - 0.75 -> middle way linearly between [selectedAlpha] and [unselectedAlpha] + * - 0.5f -> [unselectedAlpha] + * - 0.25 -> middle way linearly between [unselectedAlpha] and 0 Alpha. + * - 0.0 -> completely gone 0 Alpha. + */ fun defaultNumberPickerTextAlphaModifier( modifier: Modifier = Modifier, unselectedAlpha: Float = TextPickerDefaults.unselectedAlpha, diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerMeasurePolicy.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerMeasurePolicy.kt index f99bff1..a12b3f8 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerMeasurePolicy.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/content/DefaultTextPickerMeasurePolicy.kt @@ -8,6 +8,16 @@ import androidx.compose.ui.layout.Placeable import androidx.compose.ui.unit.Constraints import org.fknives.android.compose.picker.text.TextPickerState +/** + * Default TextMeasurePolicy of [TextPicker][org.fknives.android.compose.picker.text.TextPicker]. + * + * Intended to be used together with [TextPickerContent] and [DefaultTextPickerContent]. + * + * The expected [numberOfItemsToPlace] items are places one below the other, translated by [state]. + * Only [numberOfHeightMeasuringItems] items are counted in height, because elements are expected to be translated in and out of boundaries. + * + * Can be extended via [placementAfterItems] to place additional Items if necessary. + */ open class DefaultTextPickerMeasurePolicy(private val state: TextPickerState) : MeasurePolicy { open val numberOfItemsToPlace = 4 diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/content/LinedContent.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/content/LinedContent.kt index bccec77..af96a52 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/content/LinedContent.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/content/LinedContent.kt @@ -7,10 +7,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.Placeable import androidx.compose.ui.unit.Dp -import org.fknives.android.compose.picker.text.TextPickerContent import org.fknives.android.compose.picker.text.util.TextPickerDefaults import org.fknives.android.compose.picker.text.TextPickerState +/** + * Default [TextPickerContent] for [LinedTextPicker][org.fknives.android.compose.picker.text.LinedTextPicker]. + */ class LinedTextPickerContent( private val dividerModifier: Modifier = Modifier, private val dividerColor: @Composable () -> Color = { MaterialTheme.colors.onSurface.copy(alpha = TextPickerDefaults.dividerAlpha) }, @@ -18,20 +20,28 @@ class LinedTextPickerContent( private val dividerStartIndent: Dp = TextPickerDefaults.dividerStartIndent ) : TextPickerContent { + /** + * Creates the actual content. + * + * Calculates the indexes around [selected], gets each item's text via [textForIndex]. + * From that the [item]s are placed with their respective text and given translation. + * + * Also adds 2 Divider configured by constructor at the end. + * The Dividers are expected to be places by [LinedTextPickerMeasurePolicy]. + */ @Composable override fun Content( textForIndex: (Int) -> String, selected: Int, - moveUnsafeToProperIndex: (Int) -> Int, before1TranslatePercent: Float, itemTranslatePercent: Float, after1TranslatePercent: Float, after2TranslatePercent: Float, item: @Composable (text: String, translation: Float) -> Unit ) { - val before1 = moveUnsafeToProperIndex(selected - 1) - val after1 = moveUnsafeToProperIndex(selected + 1) - val after2 = moveUnsafeToProperIndex(selected + 2) + val before1 = selected - 1 + val after1 = selected + 1 + val after2 = selected + 2 item( text = textForIndex(before1), translation = before1TranslatePercent @@ -63,6 +73,12 @@ class LinedTextPickerContent( } } +/** + * Same as [DefaultTextPickerMeasurePolicy], with additiona Divider positioning. + * + * Expected to be used together with [LinedTextPickerContent] providing the Dividers, + * which this MeasurePolicy places around the selected Item's usual place. + */ class LinedTextPickerMeasurePolicy(state: TextPickerState) : DefaultTextPickerMeasurePolicy(state = state) { override fun Placeable.PlacementScope.placementAfterItems(placeables: List, state: TextPickerState) { diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/content/TextPickerContent.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/content/TextPickerContent.kt new file mode 100644 index 0000000..d05e40f --- /dev/null +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/content/TextPickerContent.kt @@ -0,0 +1,16 @@ +package org.fknives.android.compose.picker.text.content + +import androidx.compose.runtime.Composable + +fun interface TextPickerContent { + @Composable + fun Content( + textForIndex: (Int) -> String, + selected: Int, + before1TranslatePercent: Float, + itemTranslatePercent: Float, + after1TranslatePercent: Float, + after2TranslatePercent: Float, + item: @Composable (text: String, translation: Float) -> Unit + ) +} \ No newline at end of file diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/util/LapCounterByIndexOfDifference.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/util/LapCounterByIndexOfDifference.kt index 19b186a..aeb46a0 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/util/LapCounterByIndexOfDifference.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/util/LapCounterByIndexOfDifference.kt @@ -3,10 +3,56 @@ package org.fknives.android.compose.picker.text.util import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +/** + * Remembers the lapCounterByIndexOfDifference function with fixed [selectedIndex] and [itemCount]. + */ +@Composable +fun rememberLapsCounterByOnIndexDifference(selectedIndex: Int, itemCount: Int) = remember(selectedIndex, itemCount) { + createLapsCounterByOnIndexDifference(selectedIndex = selectedIndex, itemCount = itemCount) +} + +/** + * Curry function for [lapCounterByIndexOfDifference] + * Keeps [itemCount] and [selectedIndex] for scope. + * + * @return Returned lambda expectes the indexDifference for the [lapCounterByIndexOfDifference] calculation. + */ fun createLapsCounterByOnIndexDifference(itemCount: Int, selectedIndex: Int): (Int) -> Int = { indexDifference -> lapCounterByIndexOfDifference(itemCount = itemCount, indexDifference = indexDifference, selectedIndex = selectedIndex) } +/** + * Helper function intended to be used with onIndexDifferenceChanging of [TextPicker][org.fknives.android.compose.picker.text.TextPicker]. + * + * Calculates how many times the TextPicker were scrolled over (first element -to-> last element by index decrease or vice versa) + * Useful when OverScrolling needs some reaction. + * + * @param itemCount Number of items + * @param selectedIndex the currently selected index, must be between `0 until [itemCount]` + * @param indexDifference the current index difference from [selectedIndex]. + * + * @return The Round Around Scrolling. + * + * Example1: + * `itemCount = 3, selectedIndex = 0, indexDifference = -1.` + * This results in -1, meaning the currently shown element is index 2. 1 overscroll happened from index 0 to index 2. + * + * Example2: + * `itemCount = 3, selectedIndex = 0, indexDifference = -3.` + * This results in -1, meaning the currently shown element is index 0. 1 overscroll happened from index 0 to index 2. + * + * Example3: + * `itemCount = 3, selectedIndex = 0, indexDifference = -4.` + * This results in -2, meaning the currently shown element is index 2. 2 overscroll happened from index 0 to index 2. + * + * Example3: + * `itemCount = 3, selectedIndex = 0, indexDifference = 3.` + * This results in 1, meaning the currently shown element is index 0. 1 overscroll happened from index 2 to index 0. + * + * Example3: + * `itemCount = 3, selectedIndex = 0, indexDifference = 5.` + * This results in 1, meaning the currently shown element is index 2. 1 overscroll happened from index 2 to index 0. + */ fun lapCounterByIndexOfDifference( itemCount: Int, selectedIndex: Int, @@ -28,9 +74,4 @@ fun lapCounterByIndexOfDifference( } return counter -} - -@Composable -fun rememberLapsCounterByOnIndexDifference(selectedIndex: Int, itemCount: Int) = remember(selectedIndex, itemCount) { - createLapsCounterByOnIndexDifference(selectedIndex = selectedIndex, itemCount = itemCount) } \ No newline at end of file diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/util/OffsetLimiter.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/util/OffsetLimiter.kt index 903db30..a3c56b0 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/util/OffsetLimiter.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/util/OffsetLimiter.kt @@ -4,10 +4,24 @@ import org.fknives.android.compose.picker.text.TextPickerState import kotlin.math.max import kotlin.math.min +/** + * Scrolling Offset Limiter intended for [TextPickerAnimator][org.fknives.android.compose.picker.text.TextPickerAnimator]. + * + * Provides to [default]s: + * - [NoLimit] Used when scrolling should not be limited + * (like RoundAround ([TextPicker][org.fknives.android.compose.picker.text.TextPicker]) + * - [MinMaxLimit] Used when scrolling should be limited to maximum scroll of up to First Element and down to LastElement. + */ fun interface OffsetLimiter { + /** + * Hard limit on offset, to ensure indexes are within range. + */ fun limit(offset: Float, state: TextPickerState): Float + /** + * Soft limit on offset, to enable overshooting for animations. + */ fun overshootLimit(offset: Float, state: TextPickerState): Float = limit(offset, state) diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerDefaults.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerDefaults.kt index c17f8ff..58167ab 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerDefaults.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerDefaults.kt @@ -2,12 +2,19 @@ package org.fknives.android.compose.picker.text.util import androidx.compose.ui.unit.dp +/** + * Default values for [TextPicker][org.fknives.android.compose.picker.text.TextPicker] + */ object TextPickerDefaults { const val selected = 0 const val velocityMultiplier = 0.3f const val unselectedAlpha = 0.6f const val selectedAlpha = 1f + /** + * [TextPickerAnimator][org.fknives.android.compose.picker.text.TextPickerAnimator] checks against this to not emit unnecessary updates. + */ internal val onIndexDifferenceChanging: (Int) -> Unit = {} + const val roundAround = true const val dividerAlpha = 0.12f val dividerThickness = 2.dp diff --git a/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerStateHelper.kt b/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerStateHelper.kt index 5ef3d74..94ab5ae 100644 --- a/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerStateHelper.kt +++ b/picker/src/main/java/org/fknives/android/compose/picker/text/util/TextPickerStateHelper.kt @@ -9,6 +9,9 @@ import androidx.compose.ui.unit.Density import org.fknives.android.compose.picker.text.TextPickerAnimator import org.fknives.android.compose.picker.text.TextPickerState +/** + * Helper function caching [TextPickerState] + */ @Composable fun rememberTextPickerState(selected: Int, itemCount: Int): TextPickerState { require(selected >= 0) { "Selected value ($selected) is less than 0!" } @@ -26,6 +29,9 @@ fun rememberTextPickerState(selected: Int, itemCount: Int): TextPickerState { } } +/** + * Helper function caching [TextPickerAnimator] + */ @Composable fun rememberTextPickerAnimator( roundAround: Boolean, @@ -43,6 +49,9 @@ fun rememberTextPickerAnimator( ) } +/** + * Helper function caching [TextPickerAnimator] + */ @Composable fun rememberTextPickerAnimator( offsetLimiter: OffsetLimiter, @@ -61,6 +70,12 @@ fun rememberTextPickerAnimator( } } +/** + * Creates a Lambda changing index based on [roundAround]. + * + * If roundAround is False, means the index is not touched, since it should not behave like a wheel. + * IF roundAround is True, means the index is feed into [moveUnsafeToProperIndex] to behave like a wheel. + */ @Composable fun rememberDefaultMoveUnsafeToProperIndex(itemCount: Int, roundAround: Boolean) = if (roundAround) { @@ -69,12 +84,21 @@ fun rememberDefaultMoveUnsafeToProperIndex(itemCount: Int, roundAround: Boolean) remember { { index: Int -> index } } } - +/** + * Curry function for [moveUnsafeToProperIndex] which stores the [itemCount] + * + * @return lambda expecting the unsafeIndex to call [moveUnsafeToProperIndex] + */ fun createMoveUnsafeToProperIndex(itemCount: Int) = moveUnsafeToProperIndex@{ unsafeIndex: Int -> moveUnsafeToProperIndex(unsafeIndex, itemCount) } -fun moveUnsafeToProperIndex(unsafeIndex: Int, itemCount: Int) : Int { +/** + * Moves a given [unsafeIndex] between `0 until [itemCount]` like it is a continuous wheel. + * Meaning that `unsafeIndex=-1` becomes `itemCount-1` while `unsafeIndex=itemCount` becomes 0. + * + */ +fun moveUnsafeToProperIndex(unsafeIndex: Int, itemCount: Int): Int { var index = unsafeIndex while (index < 0) { index += itemCount @@ -85,13 +109,42 @@ fun moveUnsafeToProperIndex(unsafeIndex: Int, itemCount: Int) : Int { return index } +/** + * Caches function accessing text for given index safely. + * [roundAround] True means the Index will be modified if it needs to be to fall between the safe range for [textForIndex] + * [roundAround] False means the if the Index is falls outside of safeZone, [textForUnsafeIndex] will be used as fallback. + * Note: unsafeIndex means the index NOT between `0 until itemCount`. + */ @Composable -fun rememberWrappedTextForIndex(itemCount: Int, roundAround: Boolean, textForIndex: (Int) -> String): (Int) -> String = +fun rememberSafeTextForIndex( + textForUnsafeIndex: String = "", + itemCount: Int, + roundAround: Boolean, + textForIndex: (Int) -> String, + moveUnsafeToProperIndex: (Int) -> Int +): (Int) -> String = if (roundAround) { - textForIndex + remember(moveUnsafeToProperIndex, textForIndex) { + createSafeRoundAroundTextForIndex(moveUnsafeToProperIndex = moveUnsafeToProperIndex, textForIndex = textForIndex) + } } else { remember(itemCount, textForIndex) { - { index -> if (index < 0 || index >= itemCount) "" else textForIndex(index) } + createSafeOrDefaultTextForIndex(itemCount = itemCount, textForIndex = textForIndex, textForUnsafeIndex = textForUnsafeIndex) } } +/** + * Returns lambda, which ensures the index is moved to into safe position by [moveUnsafeToProperIndex] before accessing [textForIndex] + */ +fun createSafeRoundAroundTextForIndex(moveUnsafeToProperIndex: (Int) -> Int, textForIndex: (Int) -> String): (Int) -> String = + { index: Int -> + textForIndex(moveUnsafeToProperIndex(index)) + } + +/** + * Returns lambda, which ensures the index is between `0 until itemCount` before accessing [textForIndex], fallbacks [textForUnsafeIndex] + */ +fun createSafeOrDefaultTextForIndex(textForUnsafeIndex: String = "", itemCount: Int, textForIndex: (Int) -> String): (Int) -> String = + { index: Int -> + if (index in 0 until itemCount) textForIndex(index) else textForUnsafeIndex + } \ No newline at end of file