mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-26 14:09:58 +00:00
Convert AddAllowedMembersFragment to compose.
This commit is contained in:
committed by
Jeffrey Starke
parent
958dde0f6e
commit
ecddf34083
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.ui.compose
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.EnterExitState
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Utilizes a circular reveal animation to display and hide the content given.
|
||||
* When the content is hidden via settings [isLoading] to true, we display a
|
||||
* circular progress indicator.
|
||||
*
|
||||
* This component will automatically size itself according to the content passed
|
||||
* in via [content]
|
||||
*/
|
||||
@Composable
|
||||
fun CircularProgressWrapper(
|
||||
isLoading: Boolean,
|
||||
content: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
) {
|
||||
var size by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
val dpSize = with(LocalDensity.current) {
|
||||
DpSize(size.width.toDp(), size.height.toDp())
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isLoading,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.size(dpSize)
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = !isLoading,
|
||||
enter = EnterTransition.None,
|
||||
exit = ExitTransition.None
|
||||
) {
|
||||
val visibility = transition.animateFloat(
|
||||
transitionSpec = { tween(durationMillis = 400, easing = LinearOutSlowInEasing) },
|
||||
label = "CircularProgressWrapper-Visibility"
|
||||
) { state ->
|
||||
if (state == EnterExitState.Visible) 1f else 0f
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onSizeChanged { s ->
|
||||
size = s
|
||||
}
|
||||
.circularReveal(visibility)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
fun CircularProgressWrapperPreview() {
|
||||
var isLoading by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
LaunchedEffect(isLoading) {
|
||||
if (isLoading) {
|
||||
delay(3.seconds)
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
Previews.Preview {
|
||||
CircularProgressWrapper(
|
||||
isLoading = isLoading,
|
||||
content = {
|
||||
Buttons.LargeTonal(onClick = {
|
||||
isLoading = true
|
||||
}) {
|
||||
Text(text = "Next")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.ui.compose
|
||||
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.drawscope.clipPath
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Circle Reveal Modifiers found here:
|
||||
* https://gist.github.com/darvld/eb3844474baf2f3fc6d3ab44a4b4b5f8
|
||||
*
|
||||
* A modifier that clips the composable content using a circular shape. The radius of the circle
|
||||
* will be determined by the [transitionProgress].
|
||||
*
|
||||
* The values of the progress should be between 0 and 1.
|
||||
*
|
||||
* By default, the circle is centered in the content, but custom positions may be specified using
|
||||
* [revealFrom]. Specified offsets should be between 0 (left/top) and 1 (right/bottom).
|
||||
*/
|
||||
fun Modifier.circularReveal(
|
||||
transitionProgress: State<Float>,
|
||||
revealFrom: Offset = Offset(0.5f, 0.5f)
|
||||
): Modifier {
|
||||
return drawWithCache {
|
||||
val path = Path()
|
||||
|
||||
val center = revealFrom.mapTo(size)
|
||||
val radius = calculateRadius(revealFrom, size)
|
||||
|
||||
path.addOval(Rect(center, radius * transitionProgress.value))
|
||||
|
||||
onDrawWithContent {
|
||||
clipPath(path) { this@onDrawWithContent.drawContent() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Offset.mapTo(size: Size): Offset {
|
||||
return Offset(x * size.width, y * size.height)
|
||||
}
|
||||
|
||||
private fun calculateRadius(normalizedOrigin: Offset, size: Size) = with(normalizedOrigin) {
|
||||
val x = (if (x > 0.5f) x else 1 - x) * size.width
|
||||
val y = (if (y > 0.5f) y else 1 - y) * size.height
|
||||
|
||||
sqrt(x * x + y * y)
|
||||
}
|
||||
@@ -156,6 +156,7 @@ object Rows {
|
||||
onCheckChanged: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String? = null,
|
||||
icon: ImageVector? = null,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
isLoading: Boolean = false
|
||||
) {
|
||||
@@ -168,6 +169,15 @@ object Rows {
|
||||
.padding(defaultPadding()),
|
||||
verticalAlignment = CenterVertically
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(24.dp))
|
||||
}
|
||||
|
||||
TextAndLabel(
|
||||
text = text,
|
||||
label = label,
|
||||
|
||||
Reference in New Issue
Block a user