Skip to content
Snippets Groups Projects
Verified Commit 594d50c7 authored by Alexandru2909's avatar Alexandru2909 Committed by ma1
Browse files

Bug 1810776 - Add SwipeToDismiss to composed tabs tray

parent d3d59baf
No related branches found
No related tags found
1 merge request!56Bug 42363: Backport tab thumbnails enhancements
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.material.DismissDirection
import androidx.compose.material.DismissDirection.EndToStart
import androidx.compose.material.DismissDirection.StartToEnd
import androidx.compose.material.DismissState
import androidx.compose.material.DismissValue
import androidx.compose.material.DismissValue.Default
import androidx.compose.material.DismissValue.DismissedToEnd
import androidx.compose.material.DismissValue.DismissedToStart
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FixedThreshold
import androidx.compose.material.FractionalThreshold
import androidx.compose.material.Text
import androidx.compose.material.ThresholdConfig
import androidx.compose.material.rememberDismissState
import androidx.compose.material.swipeable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.theme.FirefoxTheme
import kotlin.math.roundToInt
/**
* A composable that can be dismissed by swiping left or right
*
* @param state The state of this component.
* @param modifier Optional [Modifier] for this component.
* @param enabled [Boolean] controlling whether the content is swipeable or not.
* @param directions The set of directions in which the component can be dismissed.
* @param dismissThreshold The threshold the item needs to be swiped in order to be dismissed.
* @param backgroundContent A composable that is stacked behind the primary content and is exposed
* when the content is swiped. You can/should use the [state] to have different backgrounds on each side.
* @param dismissContent The content that can be dismissed.
*/
@Composable
@ExperimentalMaterialApi
fun SwipeToDismiss(
state: DismissState,
modifier: Modifier = Modifier,
enabled: Boolean = true,
directions: Set<DismissDirection> = setOf(EndToStart, StartToEnd),
dismissThreshold: ThresholdConfig = FractionalThreshold(DISMISS_THRESHOLD),
backgroundContent: @Composable RowScope.() -> Unit,
dismissContent: @Composable RowScope.() -> Unit,
) {
val swipeWidth = with(LocalDensity.current) {
LocalConfiguration.current.screenWidthDp.dp.toPx()
}
val anchors = mutableMapOf(0f to Default)
val thresholds = { _: DismissValue, _: DismissValue ->
dismissThreshold
}
if (StartToEnd in directions) anchors += swipeWidth to DismissedToEnd
if (EndToStart in directions) anchors += -swipeWidth to DismissedToStart
Box(
Modifier
.swipeable(
state = state,
anchors = anchors,
thresholds = thresholds,
orientation = Orientation.Horizontal,
enabled = state.currentValue == Default && enabled,
reverseDirection = LocalLayoutDirection.current == LayoutDirection.Rtl,
resistance = null,
)
.then(modifier),
) {
Row(
content = backgroundContent,
modifier = Modifier.matchParentSize(),
)
Row(
content = dismissContent,
modifier = Modifier.offset { IntOffset(state.offset.value.roundToInt(), 0) },
)
}
}
private const val DISMISS_THRESHOLD = 0.5f
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun SwipeablePreview(directions: Set<DismissDirection>, text: String, threshold: ThresholdConfig) {
val state = rememberDismissState()
Box(
modifier = Modifier
.height(30.dp)
.fillMaxWidth(),
) {
SwipeToDismiss(
state = state,
directions = directions,
dismissThreshold = threshold,
backgroundContent = {
Box(
modifier = Modifier
.fillMaxSize()
.background(FirefoxTheme.colors.layerAccent),
)
},
) {
Row(
modifier = Modifier
.fillMaxSize()
.background(FirefoxTheme.colors.layer1),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Text(text)
}
}
}
}
@Suppress("MagicNumber")
@OptIn(ExperimentalMaterialApi::class)
@Composable
@Preview
private fun SwipeToDismissPreview() {
FirefoxTheme {
Column {
SwipeablePreview(
directions = setOf(StartToEnd),
text = "Swipe to right 50% ->",
FractionalThreshold(.5f),
)
Spacer(Modifier.height(30.dp))
SwipeablePreview(
directions = setOf(EndToStart),
text = "<- Swipe to left 100%",
FractionalThreshold(1f),
)
Spacer(Modifier.height(30.dp))
SwipeablePreview(
directions = setOf(StartToEnd, EndToStart),
text = "<- Swipe both ways 20dp ->",
FixedThreshold(20.dp),
)
}
}
}
......@@ -20,12 +20,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.DismissValue
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.rememberDismissState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
......@@ -42,6 +45,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.core.text.BidiFormatter
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
......@@ -50,6 +54,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.compose.Divider
import org.mozilla.fenix.compose.Favicon
import org.mozilla.fenix.compose.HorizontalFadingEdgeBox
import org.mozilla.fenix.compose.SwipeToDismiss
import org.mozilla.fenix.compose.ThumbnailCard
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.tabstray.TabsTrayTestTag
......@@ -71,7 +76,7 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param onClick Callback to handle when item is clicked.
* @param onLongClick Callback to handle when item is long clicked.
*/
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable
@Suppress("MagicNumber", "LongParameterList", "LongMethod")
fun TabGridItem(
......@@ -94,11 +99,30 @@ fun TabGridItem(
Modifier
}
Box(
modifier = Modifier
.wrapContentHeight()
.wrapContentWidth(),
val dismissState = rememberDismissState(
confirmStateChange = { dismissValue ->
if (dismissValue == DismissValue.DismissedToEnd || dismissValue == DismissValue.DismissedToStart) {
onCloseClick(tab)
true
} else {
false
}
},
)
SwipeToDismiss(
state = dismissState,
enabled = !multiSelectionEnabled,
backgroundContent = {},
modifier = Modifier.zIndex(
if (dismissState.dismissDirection == null) {
0f
} else {
1f
},
),
) {
Box(modifier = Modifier.wrapContentSize()) {
Card(
modifier = Modifier
.fillMaxWidth()
......@@ -185,6 +209,7 @@ fun TabGridItem(
}
}
}
}
/**
* Thumbnail specific for the [TabGridItem], which can be selected.
......
......@@ -17,9 +17,12 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.DismissValue
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.rememberDismissState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
......@@ -37,6 +40,7 @@ import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
import mozilla.components.support.ktx.kotlin.MAX_URI_LENGTH
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.SwipeToDismiss
import org.mozilla.fenix.compose.ThumbnailCard
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.ext.toShortUrl
......@@ -59,9 +63,9 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param onClick Callback to handle when item is clicked.
* @param onLongClick Callback to handle when item is long clicked.
*/
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable
@Suppress("MagicNumber")
@Suppress("MagicNumber", "LongMethod")
fun TabListItem(
tab: TabSessionState,
isSelected: Boolean = false,
......@@ -77,9 +81,29 @@ fun TabListItem(
} else {
FirefoxTheme.colors.layer1
}
val dismissState = rememberDismissState(
confirmStateChange = { dismissValue ->
if (dismissValue == DismissValue.DismissedToEnd || dismissValue == DismissValue.DismissedToStart) {
onCloseClick(tab)
true
} else {
false
}
},
)
SwipeToDismiss(
state = dismissState,
enabled = !multiSelectionEnabled,
backgroundContent = {
DismissedTabBackground(dismissState.dismissDirection, RoundedCornerShape(0.dp))
},
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(FirefoxTheme.colors.layer3)
.background(contentBackgroundColor)
.combinedClickable(
onLongClick = { onLongClick(tab) },
......@@ -140,6 +164,7 @@ fun TabListItem(
}
}
}
}
@Composable
private fun Thumbnail(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment