@@ -0,0 +1,79 @@
|
|||||||
|
package de.jeanlucmakiola.calendula.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drag distance that turns a calendar page, shared by the month, week and day
|
||||||
|
* views so all three answer a swipe at the same point.
|
||||||
|
*
|
||||||
|
* It was 6dp once, which is inside the distance a tap wanders: brushing the grid
|
||||||
|
* changed the month, and a page that turns on an unintended gesture reads as the
|
||||||
|
* animation misfiring rather than as the gesture being over-eager.
|
||||||
|
*/
|
||||||
|
val CALENDAR_SWIPE_THRESHOLD = 24.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The whole-page horizontal swipe: one page per gesture, committed **the moment
|
||||||
|
* the drag clears [CALENDAR_SWIPE_THRESHOLD]** rather than when the finger lifts.
|
||||||
|
*
|
||||||
|
* Waiting for the lift meant the page sat still under a finger that had already
|
||||||
|
* travelled far enough to ask for it, and the answer only arrived once you let
|
||||||
|
* go — which reads as the view being slow rather than as a deliberate commit.
|
||||||
|
* Firing on the threshold is what makes the gesture feel like it is being
|
||||||
|
* followed. The trade is that a drag can no longer be taken back by dragging the
|
||||||
|
* other way; in practice, once you have moved 24dp deliberately you meant it, and
|
||||||
|
* the page you land on is one swipe back.
|
||||||
|
*
|
||||||
|
* Deliberately **horizontal-only**. The week and day timelines scroll vertically
|
||||||
|
* underneath this, and a two-dimensional detector here would claim those drags
|
||||||
|
* before the inner scroll ever saw them. As it is, a horizontal drag crosses this
|
||||||
|
* detector's slop while a vertical one is consumed below, and the two coexist.
|
||||||
|
* (The month view's split style needs a vertical axis as well, so it keeps its
|
||||||
|
* own axis-locking detector rather than using this.)
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberCalendarPageSwipe(
|
||||||
|
onSwipeNext: () -> Unit,
|
||||||
|
onSwipePrev: () -> Unit,
|
||||||
|
): Modifier {
|
||||||
|
val threshold = with(LocalDensity.current) { CALENDAR_SWIPE_THRESHOLD.toPx() }
|
||||||
|
return Modifier.pointerInput(onSwipeNext, onSwipePrev) {
|
||||||
|
var accum = 0f
|
||||||
|
// One page per gesture: without this a long drag would keep re-firing
|
||||||
|
// every time the accumulator crossed the threshold again.
|
||||||
|
var fired = false
|
||||||
|
detectHorizontalDragGestures(
|
||||||
|
onDragStart = {
|
||||||
|
accum = 0f
|
||||||
|
fired = false
|
||||||
|
},
|
||||||
|
onDragEnd = {
|
||||||
|
accum = 0f
|
||||||
|
fired = false
|
||||||
|
},
|
||||||
|
onDragCancel = {
|
||||||
|
accum = 0f
|
||||||
|
fired = false
|
||||||
|
},
|
||||||
|
onHorizontalDrag = { _, drag ->
|
||||||
|
accum += drag
|
||||||
|
if (!fired) {
|
||||||
|
val commit = when {
|
||||||
|
accum < -threshold -> onSwipeNext
|
||||||
|
accum > threshold -> onSwipePrev
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (commit != null) {
|
||||||
|
fired = true
|
||||||
|
commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import androidx.compose.animation.animateColorAsState
|
|||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -43,7 +42,6 @@ import androidx.compose.material3.rememberDrawerState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -79,6 +77,7 @@ import de.jeanlucmakiola.calendula.ui.common.NowLine
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarPageSwipe
|
||||||
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
||||||
import de.jeanlucmakiola.calendula.ui.common.next
|
import de.jeanlucmakiola.calendula.ui.common.next
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
||||||
@@ -266,8 +265,6 @@ private fun DayContent(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val threshold = with(density) { 24.dp.toPx() }
|
|
||||||
var dragAccum by remember { mutableFloatStateOf(0f) }
|
|
||||||
val slideSpec = rememberCalendarSlideSpec()
|
val slideSpec = rememberCalendarSlideSpec()
|
||||||
val fadeSpec = rememberCalendarFadeSpec()
|
val fadeSpec = rememberCalendarFadeSpec()
|
||||||
val reduceMotion = rememberReduceMotion()
|
val reduceMotion = rememberReduceMotion()
|
||||||
@@ -296,20 +293,7 @@ private fun DayContent(
|
|||||||
// Whole-page horizontal swipe, one level above the timeline's vertical
|
// Whole-page horizontal swipe, one level above the timeline's vertical
|
||||||
// scroll: a horizontal drag crosses this detector's slop, while a vertical
|
// scroll: a horizontal drag crosses this detector's slop, while a vertical
|
||||||
// drag is consumed by the inner scroll first — the two gestures coexist.
|
// drag is consumed by the inner scroll first — the two gestures coexist.
|
||||||
val swipeModifier = Modifier.pointerInput(Unit) {
|
val swipeModifier = rememberCalendarPageSwipe(onSwipeNext, onSwipePrev)
|
||||||
detectHorizontalDragGestures(
|
|
||||||
onDragStart = { dragAccum = 0f },
|
|
||||||
onDragEnd = {
|
|
||||||
when {
|
|
||||||
dragAccum < -threshold -> onSwipeNext()
|
|
||||||
dragAccum > threshold -> onSwipePrev()
|
|
||||||
}
|
|
||||||
dragAccum = 0f
|
|
||||||
},
|
|
||||||
onDragCancel = { dragAccum = 0f },
|
|
||||||
onHorizontalDrag = { _, drag -> dragAccum += drag },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = state,
|
targetState = state,
|
||||||
|
|||||||
@@ -75,8 +75,12 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
@@ -100,6 +104,7 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn
|
import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn
|
||||||
import de.jeanlucmakiola.calendula.ui.common.TodayAction
|
import de.jeanlucmakiola.calendula.ui.common.TodayAction
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
|
import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.CALENDAR_SWIPE_THRESHOLD
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||||
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
||||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||||
@@ -556,7 +561,13 @@ private val WEEK_NUMBER_GUTTER = 40.dp
|
|||||||
private val DAY_NUMBER_GAP = 4.dp
|
private val DAY_NUMBER_GAP = 4.dp
|
||||||
private val CELL_TOP_PADDING = 6.dp
|
private val CELL_TOP_PADDING = 6.dp
|
||||||
private val CELL_GAP = 2.dp
|
private val CELL_GAP = 2.dp
|
||||||
private val CELL_SHAPE = RoundedCornerShape(12.dp)
|
/** Named separately because the split style's selection outline draws its own
|
||||||
|
* rounded rect and has to match this radius exactly. */
|
||||||
|
private val CELL_CORNER = 12.dp
|
||||||
|
private val CELL_SHAPE = RoundedCornerShape(CELL_CORNER)
|
||||||
|
|
||||||
|
/** Width of the split style's selected-day outline. */
|
||||||
|
private val SPLIT_SELECTION_STROKE = 1.5.dp
|
||||||
private const val MAX_EVENT_ROWS = 3
|
private const val MAX_EVENT_ROWS = 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -793,20 +804,21 @@ private enum class DragAxis { Undecided, Horizontal, Vertical }
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The month grid's drag gesture: horizontal pages the month, vertical expands or
|
* The month grid's drag gesture: horizontal pages the month, vertical expands or
|
||||||
* collapses the split style (#53). Accumulates and commits past a threshold on
|
* collapses the split style (#53). The grid doesn't follow the finger, so there is
|
||||||
* release — the grid doesn't follow the finger, so there is no distance to
|
* no distance to rubber-band against — it commits the moment the accumulated drag
|
||||||
* rubber-band against.
|
* clears the threshold, once per gesture, exactly as
|
||||||
|
* [rememberCalendarPageSwipe][de.jeanlucmakiola.calendula.ui.common.rememberCalendarPageSwipe]
|
||||||
|
* does for the week and day views.
|
||||||
*
|
*
|
||||||
* The axis is **locked on the first movement and held for the whole gesture**, so
|
* This is the month's own detector rather than that shared one because it needs
|
||||||
* a drag can page or expand but never both. Two independent detectors on one
|
* two axes, and the axis is **locked on the first movement and held for the whole
|
||||||
* surface would each see their own component of a diagonal drag and both fire.
|
* gesture** so a drag can page or expand but never both. Two independent
|
||||||
|
* detectors on one surface would each see their own component of a diagonal drag
|
||||||
|
* and both fire.
|
||||||
*
|
*
|
||||||
* The horizontal threshold matches the week and day views'. It used to be 6dp,
|
* The horizontal threshold is the shared one. The vertical is larger — swapping
|
||||||
* which is inside the distance a tap wanders: brushing the grid changed the
|
* the whole layout out deserves a more deliberate pull than stepping to the next
|
||||||
* month, and a page that turns on an unintended gesture reads as the animation
|
* month.
|
||||||
* misfiring rather than as the gesture being over-eager. The vertical one is
|
|
||||||
* larger — swapping the whole layout out deserves a more deliberate pull than
|
|
||||||
* stepping to the next month.
|
|
||||||
*
|
*
|
||||||
* [onExpand]/[onCollapse] are null for the paged style, which leaves the vertical
|
* [onExpand]/[onCollapse] are null for the paged style, which leaves the vertical
|
||||||
* axis unclaimed: the lock still happens, so a vertical drag there does nothing
|
* axis unclaimed: the lock still happens, so a vertical drag there does nothing
|
||||||
@@ -820,7 +832,7 @@ private fun rememberMonthSwipeModifier(
|
|||||||
onCollapse: (() -> Unit)? = null,
|
onCollapse: (() -> Unit)? = null,
|
||||||
): Modifier {
|
): Modifier {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val pageThreshold = with(density) { MONTH_SWIPE_THRESHOLD.toPx() }
|
val pageThreshold = with(density) { CALENDAR_SWIPE_THRESHOLD.toPx() }
|
||||||
val expandThreshold = with(density) { MONTH_EXPAND_THRESHOLD.toPx() }
|
val expandThreshold = with(density) { MONTH_EXPAND_THRESHOLD.toPx() }
|
||||||
return Modifier.pointerInput(onSwipeNext, onSwipePrev, onExpand, onCollapse) {
|
return Modifier.pointerInput(onSwipeNext, onSwipePrev, onExpand, onCollapse) {
|
||||||
var accum = Offset.Zero
|
var accum = Offset.Zero
|
||||||
@@ -833,13 +845,6 @@ private fun rememberMonthSwipeModifier(
|
|||||||
fired = false
|
fired = false
|
||||||
},
|
},
|
||||||
onDragEnd = {
|
onDragEnd = {
|
||||||
// Only paging waits for the lift; see onDrag.
|
|
||||||
if (!fired && axis == DragAxis.Horizontal) {
|
|
||||||
when {
|
|
||||||
accum.x < -pageThreshold -> onSwipeNext()
|
|
||||||
accum.x > pageThreshold -> onSwipePrev()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
accum = Offset.Zero
|
accum = Offset.Zero
|
||||||
axis = DragAxis.Undecided
|
axis = DragAxis.Undecided
|
||||||
fired = false
|
fired = false
|
||||||
@@ -860,22 +865,23 @@ private fun rememberMonthSwipeModifier(
|
|||||||
DragAxis.Vertical
|
DragAxis.Vertical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Expand and collapse fire the instant the drag clears the
|
// Both axes commit the instant the drag clears their threshold,
|
||||||
// threshold, rather than on release. Waiting for the lift left the
|
// rather than on release — see [rememberCalendarPageSwipe], which
|
||||||
// screen inert under a finger that had already asked for the thing,
|
// does the same for the week and day views.
|
||||||
// and the answer only arrived once you let go — which read as the
|
if (!fired) {
|
||||||
// gesture being slow to take rather than as a deliberate commit.
|
val commit = when (axis) {
|
||||||
//
|
DragAxis.Horizontal -> when {
|
||||||
// Paging still waits, because there the pending gesture is
|
accum.x < -pageThreshold -> onSwipeNext
|
||||||
// genuinely undecided: you can drag back the other way and settle
|
accum.x > pageThreshold -> onSwipePrev
|
||||||
// on a different month. There is nothing between expanded and
|
else -> null
|
||||||
// collapsed to change your mind about.
|
}
|
||||||
if (!fired && axis == DragAxis.Vertical) {
|
DragAxis.Vertical -> when {
|
||||||
val commit = when {
|
|
||||||
accum.y > expandThreshold -> onExpand
|
accum.y > expandThreshold -> onExpand
|
||||||
accum.y < -expandThreshold -> onCollapse
|
accum.y < -expandThreshold -> onCollapse
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
DragAxis.Undecided -> null
|
||||||
|
}
|
||||||
if (commit != null) {
|
if (commit != null) {
|
||||||
fired = true
|
fired = true
|
||||||
commit()
|
commit()
|
||||||
@@ -886,9 +892,6 @@ private fun rememberMonthSwipeModifier(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Drag distance that commits a month change, matching the week and day views. */
|
|
||||||
private val MONTH_SWIPE_THRESHOLD = 24.dp
|
|
||||||
|
|
||||||
/** Drag distance that commits an expand/collapse — deliberately longer than a page. */
|
/** Drag distance that commits an expand/collapse — deliberately longer than a page. */
|
||||||
private val MONTH_EXPAND_THRESHOLD = 48.dp
|
private val MONTH_EXPAND_THRESHOLD = 48.dp
|
||||||
|
|
||||||
@@ -1298,13 +1301,22 @@ private fun SplitDayCell(
|
|||||||
// Each cell fades its own outline, so moving the selection reads as one
|
// Each cell fades its own outline, so moving the selection reads as one
|
||||||
// mark crossing the grid rather than as an outline blinking out here and
|
// mark crossing the grid rather than as an outline blinking out here and
|
||||||
// reappearing there. Snapped under reduced motion.
|
// reappearing there. Snapped under reduced motion.
|
||||||
|
//
|
||||||
|
// Held as a State and read *inside the draw lambda below*, never unwrapped
|
||||||
|
// here. Read in composition it made every frame of the fade recompose all
|
||||||
|
// 42 cells at once, which is what made the outline shiver rather than fade.
|
||||||
|
// Kept in the draw phase, the animation touches nothing but the pixels.
|
||||||
val reduceMotion = rememberReduceMotion()
|
val reduceMotion = rememberReduceMotion()
|
||||||
val fadeSpec = rememberCalendarFadeSpec()
|
val fadeSpec = rememberCalendarFadeSpec()
|
||||||
val selection by animateFloatAsState(
|
val selection = animateFloatAsState(
|
||||||
targetValue = if (isSelected) 1f else 0f,
|
targetValue = if (isSelected) 1f else 0f,
|
||||||
animationSpec = if (reduceMotion) snap() else fadeSpec,
|
animationSpec = if (reduceMotion) snap() else fadeSpec,
|
||||||
label = "split-day-selection",
|
label = "split-day-selection",
|
||||||
)
|
)
|
||||||
|
val outlineColor = MaterialTheme.colorScheme.primary
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val outlineStroke = with(density) { SPLIT_SELECTION_STROKE.toPx() }
|
||||||
|
val outlineRadius = with(density) { CELL_CORNER.toPx() }
|
||||||
// Background and content are separate layers, mirroring how the paged row is
|
// Background and content are separate layers, mirroring how the paged row is
|
||||||
// built — and so the pill can be tagged for the morph on its own. Tagged as
|
// built — and so the pill can be tagged for the morph on its own. Tagged as
|
||||||
// one piece with its contents inside, the dots would be nested shared
|
// one piece with its contents inside, the dots would be nested shared
|
||||||
@@ -1331,11 +1343,22 @@ private fun SplitDayCell(
|
|||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.border(
|
.drawBehind {
|
||||||
width = 1.5.dp,
|
val alpha = selection.value
|
||||||
color = MaterialTheme.colorScheme.primary.copy(alpha = selection),
|
if (alpha <= 0f) return@drawBehind
|
||||||
shape = CELL_SHAPE,
|
// Inset by half the stroke: a stroke straddles the path it
|
||||||
),
|
// follows, so drawn on the bounds themselves the outer half
|
||||||
|
// would fall outside the cell and be clipped to a hairline
|
||||||
|
// along the edges.
|
||||||
|
val inset = outlineStroke / 2f
|
||||||
|
drawRoundRect(
|
||||||
|
color = outlineColor.copy(alpha = alpha),
|
||||||
|
topLeft = Offset(inset, inset),
|
||||||
|
size = Size(size.width - outlineStroke, size.height - outlineStroke),
|
||||||
|
cornerRadius = CornerRadius(outlineRadius - inset),
|
||||||
|
style = Stroke(width = outlineStroke),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize().padding(top = 4.dp),
|
modifier = Modifier.fillMaxSize().padding(top = 4.dp),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import androidx.compose.animation.animateColorAsState
|
|||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -49,7 +48,6 @@ import androidx.compose.runtime.CompositionLocalProvider
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -94,6 +92,7 @@ import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarPageSwipe
|
||||||
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
||||||
import de.jeanlucmakiola.floret.locale.currentLocale
|
import de.jeanlucmakiola.floret.locale.currentLocale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
||||||
@@ -296,8 +295,6 @@ private fun WeekContent(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val threshold = with(density) { 24.dp.toPx() }
|
|
||||||
var dragAccum by remember { mutableFloatStateOf(0f) }
|
|
||||||
val slideSpec = rememberCalendarSlideSpec()
|
val slideSpec = rememberCalendarSlideSpec()
|
||||||
val fadeSpec = rememberCalendarFadeSpec()
|
val fadeSpec = rememberCalendarFadeSpec()
|
||||||
val reduceMotion = rememberReduceMotion()
|
val reduceMotion = rememberReduceMotion()
|
||||||
@@ -329,20 +326,7 @@ private fun WeekContent(
|
|||||||
// vertical scroll: a horizontal drag only crosses *this* detector's slop,
|
// vertical scroll: a horizontal drag only crosses *this* detector's slop,
|
||||||
// while a vertical drag is consumed by the inner scroll first — so the two
|
// while a vertical drag is consumed by the inner scroll first — so the two
|
||||||
// gestures coexist without fighting.
|
// gestures coexist without fighting.
|
||||||
val swipeModifier = Modifier.pointerInput(Unit) {
|
val swipeModifier = rememberCalendarPageSwipe(onSwipeNext, onSwipePrev)
|
||||||
detectHorizontalDragGestures(
|
|
||||||
onDragStart = { dragAccum = 0f },
|
|
||||||
onDragEnd = {
|
|
||||||
when {
|
|
||||||
dragAccum < -threshold -> onSwipeNext()
|
|
||||||
dragAccum > threshold -> onSwipePrev()
|
|
||||||
}
|
|
||||||
dragAccum = 0f
|
|
||||||
},
|
|
||||||
onDragCancel = { dragAccum = 0f },
|
|
||||||
onHorizontalDrag = { _, drag -> dragAccum += drag },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = state,
|
targetState = state,
|
||||||
|
|||||||
Reference in New Issue
Block a user