This commit is contained in:
Vitor Pamplona
2024-10-08 09:24:10 -04:00
4 changed files with 262 additions and 142 deletions
@@ -24,8 +24,10 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@@ -147,6 +149,7 @@ import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -1326,24 +1329,52 @@ fun ReactionChoicePopup(
.collectAsStateWithLifecycle() .collectAsStateWithLifecycle()
val toRemove = remember { baseNote.reactedBy(accountViewModel.userProfile()).toImmutableSet() } val toRemove = remember { baseNote.reactedBy(accountViewModel.userProfile()).toImmutableSet() }
// Define animation specs
val animationDuration = 250
val fadeAnimationSpec = tween<Float>(durationMillis = animationDuration)
// Prevent multiple calls to onDismiss()
var dismissed by remember { mutableStateOf(false) }
val visibilityState = remember { MutableTransitionState(false).apply { targetState = true } }
LaunchedEffect(visibilityState.targetState) {
if (!visibilityState.targetState && !dismissed) {
delay(animationDuration.toLong())
dismissed = true
onDismiss()
}
}
Popup( Popup(
alignment = Alignment.BottomCenter, alignment = Alignment.BottomCenter,
offset = IntOffset(0, iconSizePx), offset = IntOffset(0, iconSizePx),
onDismissRequest = { onDismiss() }, onDismissRequest = { visibilityState.targetState = false },
properties = PopupProperties(focusable = true), properties = PopupProperties(focusable = true),
) { ) {
ReactionChoicePopupContent( AnimatedVisibility(
reactions, visibleState = visibilityState,
toRemove = toRemove, enter =
onClick = { reactionType -> slideInVertically(
accountViewModel.reactToOrDelete( initialOffsetY = { it / 2 },
baseNote, ) + fadeIn(animationSpec = fadeAnimationSpec),
reactionType, exit =
) slideOutVertically(
onDismiss() targetOffsetY = { it / 2 },
}, ) + fadeOut(animationSpec = fadeAnimationSpec),
onChangeAmount, ) {
) ReactionChoicePopupContent(
reactions,
toRemove = toRemove,
onClick = { reactionType ->
accountViewModel.reactToOrDelete(
baseNote,
reactionType,
)
visibilityState.targetState = false
},
onChangeAmount,
)
}
} }
} }
@@ -1505,59 +1536,87 @@ fun ZapAmountChoicePopup(
val yOffset = with(LocalDensity.current) { -popupYOffset.toPx().toInt() } val yOffset = with(LocalDensity.current) { -popupYOffset.toPx().toInt() }
// Define animation specs
val animationDuration = 250
val fadeAnimationSpec = tween<Float>(durationMillis = animationDuration)
// Prevent multiple calls to onDismiss()
var dismissed by remember { mutableStateOf(false) }
val visibilityState = remember { MutableTransitionState(false).apply { targetState = true } }
LaunchedEffect(visibilityState.targetState) {
if (!visibilityState.targetState && !dismissed) {
delay(animationDuration.toLong())
dismissed = true
onDismiss()
}
}
Popup( Popup(
alignment = Alignment.BottomCenter, alignment = Alignment.BottomCenter,
offset = IntOffset(0, yOffset), offset = IntOffset(0, yOffset),
onDismissRequest = { onDismiss() }, onDismissRequest = { visibilityState.targetState = false },
properties = PopupProperties(focusable = true), properties = PopupProperties(focusable = true),
) { ) {
FlowRow(horizontalArrangement = Arrangement.Center) { AnimatedVisibility(
zapAmountChoices.forEach { amountInSats -> visibleState = visibilityState,
Button( enter =
modifier = Modifier.padding(horizontal = 3.dp), slideInVertically(
onClick = { initialOffsetY = { it / 2 },
accountViewModel.zap( ) + fadeIn(animationSpec = fadeAnimationSpec),
baseNote, exit =
amountInSats * 1000, slideOutVertically(
null, targetOffsetY = { it / 2 },
zapMessage, ) + fadeOut(animationSpec = fadeAnimationSpec),
context, ) {
true, FlowRow(horizontalArrangement = Arrangement.Center) {
onError, zapAmountChoices.forEach { amountInSats ->
onProgress, Button(
onPayViaIntent, modifier = Modifier.padding(horizontal = 3.dp),
) onClick = {
onDismiss() accountViewModel.zap(
}, baseNote,
shape = ButtonBorder, amountInSats * 1000,
colors = null,
ButtonDefaults.buttonColors( zapMessage,
containerColor = MaterialTheme.colorScheme.primary, context,
), true,
) { onError,
Text( onProgress,
"${showAmount(amountInSats.toBigDecimal().setScale(1))}", onPayViaIntent,
color = Color.White, )
textAlign = TextAlign.Center, visibilityState.targetState = false
modifier = },
Modifier.combinedClickable( shape = ButtonBorder,
onClick = { colors =
accountViewModel.zap( ButtonDefaults.buttonColors(
baseNote, containerColor = MaterialTheme.colorScheme.primary,
amountInSats * 1000,
null,
zapMessage,
context,
true,
onError,
onProgress,
onPayViaIntent,
)
onDismiss()
},
onLongClick = { onChangeAmount() },
), ),
) ) {
Text(
"${showAmount(amountInSats.toBigDecimal().setScale(1))}",
color = Color.White,
textAlign = TextAlign.Center,
modifier =
Modifier.combinedClickable(
onClick = {
accountViewModel.zap(
baseNote,
amountInSats * 1000,
null,
zapMessage,
context,
true,
onError,
onProgress,
onPayViaIntent,
)
visibilityState.targetState = false
},
onLongClick = { onChangeAmount() },
),
)
}
} }
} }
} }
@@ -20,6 +20,12 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -38,6 +44,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -76,46 +83,56 @@ fun ChannelFabColumn(
} }
Column { Column {
if (isOpen) { AnimatedVisibility(
FloatingActionButton( visible = isOpen,
onClick = { enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
wantsToSendNewMessage = true exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
isOpen = false ) {
}, Column {
modifier = Size55Modifier, FloatingActionButton(
shape = CircleShape, onClick = {
containerColor = MaterialTheme.colorScheme.primary, wantsToSendNewMessage = true
) { isOpen = false
Text( },
text = stringRes(R.string.messages_new_message), modifier = Size55Modifier,
color = Color.White, shape = CircleShape,
textAlign = TextAlign.Center, containerColor = MaterialTheme.colorScheme.primary,
fontSize = Font12SP, ) {
) Text(
text = stringRes(R.string.messages_new_message),
color = Color.White,
textAlign = TextAlign.Center,
fontSize = Font12SP,
)
}
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToCreateChannel = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Text(
text = stringRes(R.string.messages_create_public_chat),
color = Color.White,
textAlign = TextAlign.Center,
fontSize = Font12SP,
)
}
Spacer(modifier = Modifier.height(20.dp))
} }
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToCreateChannel = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Text(
text = stringRes(R.string.messages_create_public_chat),
color = Color.White,
textAlign = TextAlign.Center,
fontSize = Font12SP,
)
}
Spacer(modifier = Modifier.height(20.dp))
} }
val rotationDegree by animateFloatAsState(
targetValue = if (isOpen) 45f else 0f,
)
FloatingActionButton( FloatingActionButton(
onClick = { isOpen = !isOpen }, onClick = { isOpen = !isOpen },
modifier = Size55Modifier, modifier = Size55Modifier,
@@ -125,7 +142,10 @@ fun ChannelFabColumn(
Icon( Icon(
imageVector = Icons.Outlined.Add, imageVector = Icons.Outlined.Add,
contentDescription = stringRes(R.string.messages_create_public_private_chat_description), contentDescription = stringRes(R.string.messages_create_public_private_chat_description),
modifier = Modifier.size(26.dp), modifier =
Modifier.size(26.dp).graphicsLayer {
rotationZ = rotationDegree
},
tint = Color.White, tint = Color.White,
) )
} }
@@ -20,6 +20,11 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -73,7 +78,11 @@ fun SummaryBar(state: NotificationSummaryState) {
UserReactionsRow(state) { showChart = !showChart } UserReactionsRow(state) { showChart = !showChart }
if (showChart) { AnimatedVisibility(
visible = showChart,
enter = slideInVertically() + expandVertically(),
exit = slideOutVertically() + shrinkVertically(),
) {
val lineChartCount = val lineChartCount =
lineChart( lineChart(
lines = lines =
@@ -25,7 +25,12 @@ import android.net.Uri
import android.os.Build import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -36,6 +41,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddPhotoAlternate import androidx.compose.material.icons.filled.AddPhotoAlternate
import androidx.compose.material.icons.filled.CameraAlt import androidx.compose.material.icons.filled.CameraAlt
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -186,44 +192,51 @@ fun NewImageButton(
ShowProgress(postViewModel) ShowProgress(postViewModel)
} else { } else {
Column { Column {
if (isOpen) { // if (isOpen) {
FloatingActionButton( AnimatedVisibility(
onClick = { visible = isOpen,
wantsToPostFromCamera = true enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
isOpen = false exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
}, ) {
modifier = Size55Modifier, Column {
shape = CircleShape, FloatingActionButton(
containerColor = MaterialTheme.colorScheme.primary, onClick = {
) { wantsToPostFromCamera = true
Icon( isOpen = false
imageVector = Icons.Default.CameraAlt, },
contentDescription = stringRes(id = R.string.upload_image), modifier = Size55Modifier,
modifier = Modifier.size(26.dp), shape = CircleShape,
tint = Color.White, containerColor = MaterialTheme.colorScheme.primary,
) ) {
Icon(
imageVector = Icons.Default.CameraAlt,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToPostFromGallery = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
imageVector = Icons.Default.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
} }
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
wantsToPostFromGallery = true
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
imageVector = Icons.Default.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
} }
FloatingActionButton( FloatingActionButton(
@@ -234,12 +247,31 @@ fun NewImageButton(
shape = CircleShape, shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary, containerColor = MaterialTheme.colorScheme.primary,
) { ) {
Icon( AnimatedVisibility(
painter = painterResource(R.drawable.ic_compose), visible = isOpen,
contentDescription = stringRes(id = R.string.new_short), enter = fadeIn(),
modifier = Modifier.size(26.dp), exit = fadeOut(),
tint = Color.White, ) {
) Icon(
imageVector = Icons.Outlined.Close,
contentDescription = stringRes(id = R.string.new_short),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
AnimatedVisibility(
visible = !isOpen,
enter = fadeIn(),
exit = fadeOut(),
) {
Icon(
painter = painterResource(R.drawable.ic_compose),
contentDescription = stringRes(id = R.string.new_short),
modifier = Modifier.size(26.dp),
tint = Color.White,
)
}
} }
} }
} }