Merge branch 'develop' into feature-oled-black
This commit is contained in:
commit
f19295d63d
291 changed files with 4973 additions and 1595 deletions
|
|
@ -16,7 +16,10 @@ import android.widget.EditText
|
|||
import androidx.appcompat.app.ActionBar.LayoutParams
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectVerticalDragGestures
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.awaitVerticalPointerSlopOrCancellation
|
||||
import androidx.compose.foundation.gestures.verticalDrag
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -41,10 +44,14 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.input.pointer.PointerInputChange
|
||||
import androidx.compose.ui.input.pointer.PointerInputScope
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.input.pointer.positionChange
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -94,7 +101,7 @@ fun ExpandableBottomSheetLayout(
|
|||
.run {
|
||||
if (isSwipeGestureEnabled) {
|
||||
pointerInput(maxBottomSheetContentHeight) {
|
||||
detectVerticalDragGestures(
|
||||
customDetectVerticalDragGestures(
|
||||
onVerticalDrag = { _, dragAmount ->
|
||||
val calculatedHeight = max(minBottomContentHeightPx, currentBottomContentHeightPx - dragAmount.roundToInt())
|
||||
val newHeight = min(calculatedMaxBottomContentHeightPx, calculatedHeight)
|
||||
|
|
@ -120,7 +127,11 @@ fun ExpandableBottomSheetLayout(
|
|||
|
||||
animatable.animateTo(destination)
|
||||
}
|
||||
}
|
||||
},
|
||||
canScroll = {
|
||||
// We only consider we can scroll in the contents if the min size matches the max size so it's maximized
|
||||
minBottomContentHeightPx == calculatedMaxBottomContentHeightPx
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -189,6 +200,45 @@ fun ExpandableBottomSheetLayout(
|
|||
)
|
||||
}
|
||||
|
||||
// The original detectVerticalDragGestures doesn't allow us to conditionally consume the initial slop event that triggers the drag,
|
||||
// which is necessary in our case to allow inner scrollables to work when the sheet is not fully expanded, so we need to re-implement it here
|
||||
private suspend fun PointerInputScope.customDetectVerticalDragGestures(
|
||||
onDragStart: (Offset) -> Unit = {},
|
||||
onDragEnd: () -> Unit = {},
|
||||
onDragCancel: () -> Unit = {},
|
||||
canScroll: () -> Boolean = { false },
|
||||
onVerticalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
|
||||
) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown(requireUnconsumed = false)
|
||||
var overSlop = 0f
|
||||
val drag =
|
||||
awaitVerticalPointerSlopOrCancellation(down.id, down.type) { change, over ->
|
||||
// Consuming this event is what triggers the dragging instead of the inner content scrolling
|
||||
// We should only consume it if we can't scroll in the inner content so we drag the bottom sheet instead, otherwise we let it pass through
|
||||
// This is the only change compared to the original detectVerticalDragGestures implementation
|
||||
if (!canScroll()) {
|
||||
change.consume()
|
||||
}
|
||||
overSlop = over
|
||||
}
|
||||
if (drag != null) {
|
||||
onDragStart.invoke(drag.position)
|
||||
onVerticalDrag.invoke(drag, overSlop)
|
||||
if (
|
||||
verticalDrag(drag.id) {
|
||||
onVerticalDrag(it, it.positionChange().y)
|
||||
it.consume()
|
||||
}
|
||||
) {
|
||||
onDragEnd()
|
||||
} else {
|
||||
onDragCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
@Suppress("UnusedPrivateMember")
|
||||
|
|
|
|||
|
|
@ -32,9 +32,13 @@ import androidx.compose.ui.graphics.asImageBitmap
|
|||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.graphics.withSave
|
||||
import coil3.Image
|
||||
import coil3.ImageLoader
|
||||
|
|
@ -50,6 +54,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
|||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
|
||||
private val PIN_WIDTH = 42.dp
|
||||
private val PIN_HEIGHT = PIN_WIDTH * 1.2f
|
||||
|
|
@ -99,21 +104,33 @@ fun LocationPin(
|
|||
fun rememberLocationPinBitmap(variant: PinVariant): ImageBitmap? {
|
||||
val context = LocalContext.current
|
||||
val density = LocalDensity.current
|
||||
val imageLoader = SingletonImageLoader.get(context)
|
||||
val colors = pinColors(variant)
|
||||
val cacheKey = rememberCacheKey(variant)
|
||||
return produceState<ImageBitmap?>(initialValue = null, cacheKey) {
|
||||
val memoryCacheKey = MemoryCache.Key(cacheKey)
|
||||
val cached = imageLoader.memoryCache?.get(memoryCacheKey)
|
||||
if (cached != null) {
|
||||
value = cached.image.toBitmap().asImageBitmap()
|
||||
} else {
|
||||
val dimensions = PinDimensions(density)
|
||||
val bitmap = LocationPinRenderer.renderPin(variant, colors, dimensions, context, imageLoader)
|
||||
imageLoader.memoryCache?.set(memoryCacheKey, MemoryCache.Value(bitmap.asImage()))
|
||||
value = bitmap.asImageBitmap()
|
||||
}
|
||||
}.value
|
||||
val resources = LocalResources.current
|
||||
|
||||
return if (LocalInspectionMode.current) {
|
||||
// In preview mode, skip async loading and return a simple placeholder image instead to avoid using ImageLoader
|
||||
val dimensions = PinDimensions(density)
|
||||
val avatarImage = ResourcesCompat.getDrawable(resources, CommonDrawables.sample_avatar, context.theme)?.toBitmap()?.asImage()
|
||||
LocationPinRenderer.renderPin(variant, colors, dimensions, avatarImage).asImageBitmap()
|
||||
} else {
|
||||
produceState<ImageBitmap?>(initialValue = null, cacheKey) {
|
||||
val imageLoader = SingletonImageLoader.get(context)
|
||||
val memoryCacheKey = MemoryCache.Key(cacheKey)
|
||||
val cached = imageLoader.memoryCache?.get(memoryCacheKey)
|
||||
if (cached != null) {
|
||||
value = cached.image.toBitmap().asImageBitmap()
|
||||
} else {
|
||||
val dimensions = PinDimensions(density)
|
||||
val bitmap = with(LocationPinRenderer) {
|
||||
val avatarImage = loadAvatarImage(variant, context, imageLoader)
|
||||
renderPin(variant, colors, dimensions, avatarImage)
|
||||
}
|
||||
imageLoader.memoryCache?.set(memoryCacheKey, MemoryCache.Value(bitmap.asImage()))
|
||||
value = bitmap.asImageBitmap()
|
||||
}
|
||||
}.value
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -208,19 +225,17 @@ private object LocationPinRenderer {
|
|||
/**
|
||||
* Renders a pin variant to bitmap. Suspending for async avatar loading.
|
||||
*/
|
||||
suspend fun renderPin(
|
||||
fun renderPin(
|
||||
variant: PinVariant,
|
||||
colors: PinColors,
|
||||
dimensions: PinDimensions,
|
||||
context: Context,
|
||||
imageLoader: ImageLoader,
|
||||
avatarImage: Image?,
|
||||
): Bitmap {
|
||||
val bitmap = createBitmap(dimensions.pinWidth.toInt(), dimensions.pinHeight.toInt())
|
||||
val canvas = Canvas(bitmap)
|
||||
canvas.drawPinShape(colors.fill, colors.stroke, dimensions)
|
||||
when (variant) {
|
||||
is PinVariant.UserLocation -> {
|
||||
val avatarImage = loadAvatarImage(variant.avatarData, context, imageLoader)
|
||||
canvas.drawAvatar(
|
||||
avatarImage = avatarImage,
|
||||
avatarData = variant.avatarData,
|
||||
|
|
@ -284,11 +299,15 @@ private object LocationPinRenderer {
|
|||
return path
|
||||
}
|
||||
|
||||
private suspend fun loadAvatarImage(
|
||||
avatarData: AvatarData,
|
||||
suspend fun loadAvatarImage(
|
||||
variant: PinVariant,
|
||||
context: Context,
|
||||
imageLoader: ImageLoader,
|
||||
): Image? {
|
||||
val avatarData = when (variant) {
|
||||
is PinVariant.UserLocation -> variant.avatarData
|
||||
else -> return null
|
||||
}
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(avatarData)
|
||||
// Disable hardware rendering for Canvas
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ val SemanticColors.pinnedMessageBannerIndicator
|
|||
val SemanticColors.pinnedMessageBannerBorder
|
||||
get() = if (isLight) LightColorTokens.colorAlphaGray400 else DarkColorTokens.colorAlphaGray400
|
||||
|
||||
val SemanticColors.floatingDateBadgeBackground
|
||||
get() = if (isLight) bgCanvasDefault else bgSubtlePrimary
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ColorAliasesPreview() = ElementPreview {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue