Skip to content

Commit ba1b593

Browse files
committed
refactor(design-system): use Dp for SwipeBehaviour threshold
- Changed the `threshold` property in `SwipeBehaviour` from a percentage-based `Float` to a fixed `Dp` value. - Updated `SwipeableRowState` to calculate relative percentage thresholds dynamically based on layout width and screen density. - Updated the UI catalog to reflect these changes with a DP-based slider.
1 parent 8f63797 commit ba1b593

5 files changed

Lines changed: 41 additions & 27 deletions

File tree

app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/molecule/items/SwipeableRowItems.kt

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.RowScope
77
import androidx.compose.foundation.layout.fillMaxSize
88
import androidx.compose.foundation.layout.fillMaxWidth
99
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.width
1011
import androidx.compose.foundation.rememberScrollState
1112
import androidx.compose.foundation.verticalScroll
1213
import androidx.compose.material3.Slider
@@ -20,8 +21,11 @@ import androidx.compose.runtime.rememberCoroutineScope
2021
import androidx.compose.runtime.setValue
2122
import androidx.compose.ui.Modifier
2223
import androidx.compose.ui.graphics.lerp
24+
import androidx.compose.ui.platform.LocalDensity
2325
import androidx.compose.ui.text.style.TextAlign
2426
import androidx.compose.ui.text.style.TextOverflow
27+
import androidx.compose.ui.unit.Dp
28+
import androidx.compose.ui.unit.dp
2529
import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
2630
import app.k9mail.core.ui.compose.designsystem.atom.Surface
2731
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
@@ -80,31 +84,33 @@ fun SwipeableRowItems(modifier: Modifier = Modifier) {
8084
TextTitleLarge(text = "Swipeable Row Items")
8185
DividerHorizontal()
8286

83-
var threshold by remember { mutableFloatStateOf(value = 0.5f) }
87+
var threshold by remember { mutableFloatStateOf(value = 150f) }
88+
val thresholdDp = threshold.dp
8489
Column(verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default)) {
8590
TextLabelLarge(text = "Control the threshold to trigger the swipe: ")
8691
Slider(
8792
value = threshold,
8893
onValueChange = { threshold = it },
89-
valueRange = 0f..1f,
94+
valueRange = 100f..350f,
9095
steps = 9,
9196
modifier = Modifier.padding(horizontal = MainTheme.spacings.triple),
9297
)
93-
TextLabelSmall(text = "Current selected: ${threshold * 100}% of the content's width.")
98+
val thresholdPx = with(LocalDensity.current) { thresholdDp.toPx() }
99+
TextLabelSmall(text = "Current selected: ${threshold}dp (${thresholdPx}px).")
94100
}
95101

96102
SwipeSection(
97-
threshold = threshold,
103+
threshold = thresholdDp,
98104
subtitle = "Swipe to Dismiss",
99105
onSwipeEnd = ::onSwipeEnd,
100106
) { threshold -> SwipeBehaviour.Dismiss(threshold = threshold) }
101107
SwipeSection(
102-
threshold = threshold,
108+
threshold = thresholdDp,
103109
subtitle = "Swipe to Reveal",
104110
onSwipeEnd = ::onSwipeEnd,
105111
) { threshold -> SwipeBehaviour.Reveal(threshold = threshold) }
106112
SwipeSection(
107-
threshold = threshold,
113+
threshold = thresholdDp,
108114
subtitle = "Swipe to Reveal with Auto reset",
109115
onSwipeEnd = ::onSwipeEnd,
110116
) { threshold ->
@@ -116,10 +122,10 @@ fun SwipeableRowItems(modifier: Modifier = Modifier) {
116122

117123
@Composable
118124
private fun SwipeSection(
119-
threshold: Float,
125+
threshold: Dp,
120126
subtitle: String,
121127
onSwipeEnd: (SwipeDirection) -> Unit,
122-
behaviourFactory: (Float) -> SwipeBehaviour,
128+
behaviourFactory: (Dp) -> SwipeBehaviour,
123129
) {
124130
TextTitleMedium(text = subtitle)
125131
DividerHorizontal()
@@ -263,7 +269,13 @@ private fun SwipeableRowItems(
263269
TextBodyLarge(
264270
text = backgroundItemText(direction),
265271
modifier = Modifier
266-
.fillMaxWidth(fraction = if (behaviour is SwipeBehaviour.Reveal) behaviour.threshold else 1f)
272+
.then(
273+
if (behaviour is SwipeBehaviour.Reveal) {
274+
Modifier.width(behaviour.threshold)
275+
} else {
276+
Modifier.fillMaxWidth()
277+
},
278+
)
267279
.swipeableCommonTextPadding(),
268280
textAlign = when (direction) {
269281
SwipeDirection.StartToEnd -> TextAlign.Start

core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/SwipeBehaviour.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package net.thunderbird.core.ui.compose.designsystem.molecule.swipe
22

3-
import androidx.annotation.FloatRange
43
import androidx.compose.animation.ExitTransition
54
import androidx.compose.animation.core.AnimationSpec
65
import androidx.compose.animation.core.FastOutSlowInEasing
@@ -9,6 +8,8 @@ import androidx.compose.animation.core.tween
98
import androidx.compose.animation.fadeOut
109
import androidx.compose.animation.shrinkVertically
1110
import androidx.compose.runtime.Immutable
11+
import androidx.compose.ui.unit.Dp
12+
import androidx.compose.ui.unit.dp
1213
import kotlin.time.Duration
1314
import kotlin.time.Duration.Companion.milliseconds
1415
import net.thunderbird.core.ui.compose.designsystem.molecule.swipe.SwipeBehaviour.Companion.DEFAULT_AUTO_RESET_DELAY_MILLIS
@@ -40,8 +41,7 @@ sealed interface SwipeBehaviour {
4041
* A lower threshold makes it easier to trigger swipe actions, while a higher
4142
* threshold requires more deliberate swipe gestures.
4243
*/
43-
@get:FloatRange(from = 0.25, to = 1.0)
44-
val threshold: Float
44+
val threshold: Dp
4545

4646
/**
4747
* The animation specification used when the swipe gesture ends and the row settles to its
@@ -71,8 +71,7 @@ sealed interface SwipeBehaviour {
7171
* is enabled. Defaults to [DEFAULT_AUTO_RESET_DELAY_MILLIS].
7272
*/
7373
data class Reveal(
74-
@get:FloatRange(from = 0.25, to = 1.0)
75-
override val threshold: Float = DEFAULT_THRESHOLD,
74+
override val threshold: Dp = DEFAULT_THRESHOLD,
7675
override val settleAnimationSpec: AnimationSpec<Float> = DefaultSettleAnimation,
7776
override val enableHapticFeedback: Boolean = true,
7877
val autoReset: Boolean = false,
@@ -91,8 +90,7 @@ sealed interface SwipeBehaviour {
9190
* item is dismissed.
9291
*/
9392
data class Dismiss(
94-
@get:FloatRange(from = 0.25, to = 1.0)
95-
override val threshold: Float = DEFAULT_THRESHOLD,
93+
override val threshold: Dp = DEFAULT_THRESHOLD,
9694
override val settleAnimationSpec: AnimationSpec<Float> = DefaultDismissAnimation,
9795
override val enableHapticFeedback: Boolean = true,
9896
val dismissTransition: ExitTransition = fadeOut(tween(durationMillis = 150)) + shrinkVertically(
@@ -110,13 +108,13 @@ sealed interface SwipeBehaviour {
110108
* [SwipeableRow] while keeping the component structure intact.
111109
*/
112110
data object Disabled : SwipeBehaviour {
113-
override val threshold: Float = 1f
111+
override val threshold: Dp = Dp.Unspecified
114112
override val settleAnimationSpec: AnimationSpec<Float> = DefaultSettleAnimation
115113
override val enableHapticFeedback: Boolean = false
116114
}
117115

118116
companion object {
119-
internal const val DEFAULT_THRESHOLD = 0.5f
117+
internal val DEFAULT_THRESHOLD = 150.dp
120118
internal const val DEFAULT_AUTO_RESET_DELAY_MILLIS = 500L
121119

122120
/**

core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/SwipeableRowState.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class SwipeableRowState internal constructor(
102102
if (layoutWidth == 0f) {
103103
0f
104104
} else {
105-
(animatedOffset.value.absoluteValue / (activeBehaviour.threshold * layoutWidth)).coerceIn(0f, 1f)
105+
(animatedOffset.value.absoluteValue / (activeBehaviour.percentageThreshold * layoutWidth)).coerceIn(0f, 1f)
106106
}
107107
}
108108

@@ -219,7 +219,9 @@ class SwipeableRowState internal constructor(
219219
val targetBehaviour = if (targetOffset >= 0f) startToEndBehaviour else endToStartBehaviour
220220
val targetMaxOffset = when (targetBehaviour) {
221221
is SwipeBehaviour.Dismiss -> layoutWidth
222-
is SwipeBehaviour.Reveal -> (targetBehaviour.threshold * layoutWidth) + SWIPE_BEHAVIOUR_REVEAL_EXTENSION
222+
is SwipeBehaviour.Reveal ->
223+
(targetBehaviour.percentageThreshold * layoutWidth) + SWIPE_BEHAVIOUR_REVEAL_EXTENSION
224+
223225
is SwipeBehaviour.Disabled -> 0f
224226
}
225227

@@ -338,7 +340,7 @@ class SwipeableRowState internal constructor(
338340

339341
private fun willSettlePastThreshold(velocity: Float, behaviour: SwipeBehaviour): Boolean {
340342
val decayTarget = decayAnimationSpec.calculateTargetValue(animatedOffset.value, velocity)
341-
val finalThreshold = behaviour.threshold * layoutWidth
343+
val finalThreshold = behaviour.percentageThreshold * layoutWidth
342344
val wouldFlingPastThreshold = decayTarget.absoluteValue >= finalThreshold
343345
val hasOffsetPassedThreshold = animatedOffset.value.absoluteValue >= finalThreshold
344346
return hasOffsetPassedThreshold || wouldFlingPastThreshold
@@ -353,7 +355,7 @@ class SwipeableRowState internal constructor(
353355
is SwipeBehaviour.Dismiss if willSettlePastThreshold ->
354356
layoutWidth * SWIPE_BEHAVIOUR_DISMISS_OFFSCREEN_MULTIPLIER
355357

356-
is SwipeBehaviour.Reveal if willSettlePastThreshold -> behaviour.threshold * layoutWidth
358+
is SwipeBehaviour.Reveal if willSettlePastThreshold -> behaviour.percentageThreshold * layoutWidth
357359
is SwipeBehaviour.Disabled -> 0f
358360
else -> 0f
359361
}
@@ -406,6 +408,9 @@ class SwipeableRowState internal constructor(
406408
}
407409
}
408410

411+
private val SwipeBehaviour.percentageThreshold: Float
412+
get() = with(density) { threshold.toPx() } / layoutWidth
413+
409414
/**
410415
* Manages accessibility-related state and actions for the swipeable row component.
411416
*

core/ui/compose/designsystem/src/test/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/SwipeableRowTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,8 @@ class SwipeableRowTest : ComposeTest() {
457457
// Arrange
458458
setContentWithTheme {
459459
TestSwipeableRow(
460-
startToEndBehaviour = SwipeBehaviour.Reveal(threshold = 0.25f),
461-
endToStartBehaviour = SwipeBehaviour.Reveal(threshold = 0.25f),
460+
startToEndBehaviour = SwipeBehaviour.Reveal(threshold = 25.dp),
461+
endToStartBehaviour = SwipeBehaviour.Reveal(threshold = 25.dp),
462462
)
463463
}
464464

@@ -715,7 +715,7 @@ class SwipeableRowTest : ComposeTest() {
715715
const val FLING_HIGH_VELOCITY = 5_000f
716716
const val FLING_LOW_VELOCITY = 200f
717717
const val FLING_SHORT_DISTANCE = 100f
718-
const val FLING_HIGH_THRESHOLD = 0.9f
718+
val FLING_HIGH_THRESHOLD = 900.dp
719719
}
720720
}
721721

feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/MessageListScreenRenderer.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import androidx.compose.runtime.LaunchedEffect
1515
import androidx.compose.runtime.getValue
1616
import androidx.compose.runtime.remember
1717
import androidx.compose.runtime.rememberUpdatedState
18-
import androidx.compose.runtime.setValue
1918
import androidx.compose.runtime.snapshotFlow
2019
import androidx.compose.ui.Alignment
2120
import androidx.compose.ui.Modifier
@@ -141,7 +140,7 @@ private val SwipeAction.behaviour: SwipeBehaviour
141140
SwipeAction.ToggleStar,
142141
SwipeAction.ArchiveSetupArchiveFolder,
143142
->
144-
SwipeBehaviour.Reveal(threshold = 0.25f, autoReset = true)
143+
SwipeBehaviour.Reveal(autoReset = true)
145144

146145
SwipeAction.Archive,
147146
SwipeAction.Delete,

0 commit comments

Comments
 (0)