mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-17 13:21:50 +01:00
Adding secret emojis to reactions
This commit is contained in:
parent
8722f37fa5
commit
9cd73c2663
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||||
|
|
||||||
fun String.isUTF16Char(pos: Int): Boolean = Character.charCount(this.codePointAt(pos)) == 2
|
fun String.isUTF16Char(pos: Int): Boolean = Character.charCount(this.codePointAt(pos)) == 2
|
||||||
@ -125,5 +126,9 @@ fun String.firstFullCharOrEmoji(tags: ImmutableListOfLists<String>): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (EmojiCoder.isCoded(this)) {
|
||||||
|
return EmojiCoder.cropToFirstMessage(this)
|
||||||
|
}
|
||||||
|
|
||||||
return firstFullChar()
|
return firstFullChar()
|
||||||
}
|
}
|
||||||
|
@ -38,14 +38,19 @@ import androidx.compose.ui.graphics.PathEffect
|
|||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.graphics.StrokeJoin
|
import androidx.compose.ui.graphics.StrokeJoin
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimatedBorderTextCornerRadius(
|
fun AnimatedBorderTextCornerRadius(
|
||||||
text: String,
|
text: String,
|
||||||
modifier: Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
color: Color = Color.Unspecified,
|
||||||
|
textAlign: TextAlign? = null,
|
||||||
|
fontSize: TextUnit = 12.sp,
|
||||||
) {
|
) {
|
||||||
val infiniteTransition = rememberInfiniteTransition()
|
val infiniteTransition = rememberInfiniteTransition()
|
||||||
val animatedFloatRestart =
|
val animatedFloatRestart =
|
||||||
@ -61,7 +66,7 @@ fun AnimatedBorderTextCornerRadius(
|
|||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
fontSize = 12.sp,
|
fontSize = fontSize,
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
@ -84,6 +89,8 @@ fun AnimatedBorderTextCornerRadius(
|
|||||||
.CornerRadius(6.dp.toPx()),
|
.CornerRadius(6.dp.toPx()),
|
||||||
)
|
)
|
||||||
}.padding(3.dp),
|
}.padding(3.dp),
|
||||||
|
color = color,
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
@ -53,15 +54,24 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.window.Popup
|
||||||
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.NoteState
|
import com.vitorpamplona.amethyst.model.NoteState
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.CachedRichTextParser
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.CoreSecretMessage
|
||||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
@ -92,6 +102,7 @@ import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
|||||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
@ -212,7 +223,18 @@ fun RenderLikeGallery(
|
|||||||
when (val shortReaction = reactionType) {
|
when (val shortReaction = reactionType) {
|
||||||
"+" -> LikedIcon(modifier.size(Size19dp))
|
"+" -> LikedIcon(modifier.size(Size19dp))
|
||||||
"-" -> Text(text = "\uD83D\uDC4E", modifier = modifier)
|
"-" -> Text(text = "\uD83D\uDC4E", modifier = modifier)
|
||||||
else -> Text(text = shortReaction, modifier = modifier)
|
else -> {
|
||||||
|
if (EmojiCoder.isCoded(shortReaction)) {
|
||||||
|
DisplaySecretEmojiAsReaction(
|
||||||
|
shortReaction,
|
||||||
|
modifier,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(text = shortReaction, modifier = modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,6 +244,57 @@ fun RenderLikeGallery(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplaySecretEmojiAsReaction(
|
||||||
|
reaction: String,
|
||||||
|
modifier: Modifier,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
var secretContent by remember {
|
||||||
|
mutableStateOf<RichTextViewerState?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
var showPopup by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(reaction) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
secretContent =
|
||||||
|
CachedRichTextParser.parseText(
|
||||||
|
EmojiCoder.decode(reaction),
|
||||||
|
EmptyTagList,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val localSecretContent = secretContent
|
||||||
|
|
||||||
|
AnimatedBorderTextCornerRadius(
|
||||||
|
reaction,
|
||||||
|
modifier.clickable {
|
||||||
|
showPopup = !showPopup
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (localSecretContent != null && showPopup) {
|
||||||
|
val iconSizePx = with(LocalDensity.current) { -24.dp.toPx().toInt() }
|
||||||
|
|
||||||
|
Popup(
|
||||||
|
alignment = Alignment.TopCenter,
|
||||||
|
offset = IntOffset(0, -iconSizePx),
|
||||||
|
onDismissRequest = { showPopup = false },
|
||||||
|
properties = PopupProperties(focusable = true),
|
||||||
|
) {
|
||||||
|
Surface(Modifier.padding(10.dp)) {
|
||||||
|
val color = remember { mutableStateOf(Color.Transparent) }
|
||||||
|
CoreSecretMessage(localSecretContent, null, 3, color, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DecryptAndRenderZapGallery(
|
fun DecryptAndRenderZapGallery(
|
||||||
multiSetCard: MultiSetCard,
|
multiSetCard: MultiSetCard,
|
||||||
|
@ -101,11 +101,13 @@ import androidx.lifecycle.distinctUntilChanged
|
|||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||||
@ -953,7 +955,16 @@ private fun RenderReactionType(
|
|||||||
when (reactionType) {
|
when (reactionType) {
|
||||||
"+" -> LikedIcon(iconSizeModifier)
|
"+" -> LikedIcon(iconSizeModifier)
|
||||||
"-" -> Text(text = "\uD83D\uDC4E", maxLines = 1, fontSize = iconFontSize)
|
"-" -> Text(text = "\uD83D\uDC4E", maxLines = 1, fontSize = iconFontSize)
|
||||||
else -> Text(text = reactionType, maxLines = 1, fontSize = iconFontSize)
|
else -> {
|
||||||
|
if (EmojiCoder.isCoded(reactionType)) {
|
||||||
|
AnimatedBorderTextCornerRadius(
|
||||||
|
reactionType,
|
||||||
|
fontSize = iconFontSize,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(text = reactionType, maxLines = 1, fontSize = iconFontSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1501,6 +1512,7 @@ fun ReactionChoicePopupPeeview() {
|
|||||||
"\uD83D\uDE31",
|
"\uD83D\uDE31",
|
||||||
"\uD83E\uDD14",
|
"\uD83E\uDD14",
|
||||||
"\uD83D\uDE31",
|
"\uD83D\uDE31",
|
||||||
|
"\uD83D\uDE80\uDB40\uDD58\uDB40\uDD64\uDB40\uDD64\uDB40\uDD60\uDB40\uDD63\uDB40\uDD2A\uDB40\uDD1F\uDB40\uDD1F\uDB40\uDD53\uDB40\uDD54\uDB40\uDD5E\uDB40\uDD1E\uDB40\uDD63\uDB40\uDD51\uDB40\uDD64\uDB40\uDD55\uDB40\uDD5C\uDB40\uDD5C\uDB40\uDD59\uDB40\uDD64\uDB40\uDD55\uDB40\uDD1E\uDB40\uDD55\uDB40\uDD51\uDB40\uDD62\uDB40\uDD64\uDB40\uDD58\uDB40\uDD1F\uDB40\uDD29\uDB40\uDD24\uDB40\uDD27\uDB40\uDD55\uDB40\uDD24\uDB40\uDD51\uDB40\uDD52\uDB40\uDD22\uDB40\uDD54\uDB40\uDD23\uDB40\uDD21\uDB40\uDD21\uDB40\uDD25\uDB40\uDD52\uDB40\uDD55\uDB40\uDD25\uDB40\uDD26\uDB40\uDD25\uDB40\uDD51\uDB40\uDD24\uDB40\uDD29\uDB40\uDD53\uDB40\uDD56\uDB40\uDD25\uDB40\uDD54\uDB40\uDD52\uDB40\uDD20\uDB40\uDD22\uDB40\uDD25\uDB40\uDD25\uDB40\uDD29\uDB40\uDD56\uDB40\uDD23\uDB40\uDD21\uDB40\uDD20\uDB40\uDD53\uDB40\uDD51\uDB40\uDD20\uDB40\uDD51\uDB40\uDD26\uDB40\uDD54\uDB40\uDD54\uDB40\uDD56\uDB40\uDD54\uDB40\uDD54\uDB40\uDD52\uDB40\uDD54\uDB40\uDD24\uDB40\uDD52\uDB40\uDD54\uDB40\uDD28\uDB40\uDD53\uDB40\uDD52\uDB40\uDD55\uDB40\uDD53\uDB40\uDD24\uDB40\uDD24\uDB40\uDD29\uDB40\uDD29\uDB40\uDD25\uDB40\uDD53\uDB40\uDD22\uDB40\uDD55\uDB40\uDD27\uDB40\uDD1E\uDB40\uDD67\uDB40\uDD55\uDB40\uDD52\uDB40\uDD60",
|
||||||
),
|
),
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onChangeAmount = {},
|
onChangeAmount = {},
|
||||||
@ -1554,12 +1566,20 @@ fun RenderReaction(reactionType: String) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Text(
|
if (EmojiCoder.isCoded(reactionType)) {
|
||||||
reactionType,
|
AnimatedBorderTextCornerRadius(
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
reactionType,
|
||||||
maxLines = 1,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
fontSize = 22.sp,
|
fontSize = 20.sp,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
reactionType,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
maxLines = 1,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
@ -77,9 +78,14 @@ import androidx.lifecycle.map
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
|
import com.vitorpamplona.amethyst.service.CachedRichTextParser
|
||||||
import com.vitorpamplona.amethyst.service.firstFullChar
|
import com.vitorpamplona.amethyst.service.firstFullChar
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.CoreSecretMessage
|
||||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
@ -115,7 +121,13 @@ class UpdateReactionTypeViewModel : ViewModel() {
|
|||||||
fun toListOfChoices(commaSeparatedAmounts: String): List<Long> = commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 }
|
fun toListOfChoices(commaSeparatedAmounts: String): List<Long> = commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 }
|
||||||
|
|
||||||
fun addChoice() {
|
fun addChoice() {
|
||||||
val newValue = nextChoice.text.trim().firstFullChar()
|
val newValue =
|
||||||
|
if (EmojiCoder.isCoded(nextChoice.text)) {
|
||||||
|
EmojiCoder.cropToFirstMessage(nextChoice.text)
|
||||||
|
} else {
|
||||||
|
nextChoice.text.trim().firstFullChar()
|
||||||
|
}
|
||||||
|
|
||||||
reactionSet = reactionSet + newValue
|
reactionSet = reactionSet + newValue
|
||||||
|
|
||||||
nextChoice = TextFieldValue("")
|
nextChoice = TextFieldValue("")
|
||||||
@ -341,14 +353,77 @@ private fun RenderReactionOption(
|
|||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
else ->
|
else -> {
|
||||||
Text(
|
if (EmojiCoder.isCoded(reactionType)) {
|
||||||
text = "$reactionType ✖",
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
AnimatedBorderTextCornerRadius(
|
||||||
textAlign = TextAlign.Center,
|
reactionType,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = " ✖",
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "$reactionType ✖",
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplaySecretEmoji(
|
||||||
|
text: String,
|
||||||
|
state: RichTextViewerState,
|
||||||
|
callbackUri: String?,
|
||||||
|
canPreview: Boolean,
|
||||||
|
quotesLeft: Int,
|
||||||
|
backgroundColor: MutableState<Color>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
if (canPreview && quotesLeft > 0) {
|
||||||
|
var secretContent by remember {
|
||||||
|
mutableStateOf<RichTextViewerState?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
var showPopup by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(text) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
secretContent =
|
||||||
|
CachedRichTextParser.parseText(
|
||||||
|
EmojiCoder.decode(text),
|
||||||
|
state.tags,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val localSecretContent = secretContent
|
||||||
|
|
||||||
|
AnimatedBorderTextCornerRadius(
|
||||||
|
text,
|
||||||
|
Modifier.clickable {
|
||||||
|
showPopup = !showPopup
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (localSecretContent != null && showPopup) {
|
||||||
|
CoreSecretMessage(localSecretContent, callbackUri, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,4 +106,27 @@ object EmojiCoder {
|
|||||||
val decodedArray = ByteArray(decoded.size) { decoded[it].toByte() }
|
val decodedArray = ByteArray(decoded.size) { decoded[it].toByte() }
|
||||||
return String(decodedArray, Charsets.UTF_8)
|
return String(decodedArray, Charsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun cropToFirstMessage(text: String): String {
|
||||||
|
val decoded = mutableListOf<Int>()
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
while (i < text.length) {
|
||||||
|
val codePoint = text.codePointAt(i)
|
||||||
|
val byte = fromVariationSelector(codePoint)
|
||||||
|
|
||||||
|
if (byte == null && decoded.isNotEmpty()) {
|
||||||
|
break
|
||||||
|
} else if (byte == null) {
|
||||||
|
i += Character.charCount(codePoint) // Advance index by correct number of chars
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded.add(byte)
|
||||||
|
i += Character.charCount(codePoint) // Advance index by correct number of chars
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.substring(0, i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ class EmojiCoderTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val HELLO_WORLD = "\uD83D\uDE00\uDB40\uDD38\uDB40\uDD55\uDB40\uDD5C\uDB40\uDD5C\uDB40\uDD5F\uDB40\uDD1C\uDB40\uDD10\uDB40\uDD47\uDB40\uDD5F\uDB40\uDD62\uDB40\uDD5C\uDB40\uDD54\uDB40\uDD11"
|
val HELLO_WORLD = "\uD83D\uDE00\uDB40\uDD38\uDB40\uDD55\uDB40\uDD5C\uDB40\uDD5C\uDB40\uDD5F\uDB40\uDD1C\uDB40\uDD10\uDB40\uDD47\uDB40\uDD5F\uDB40\uDD62\uDB40\uDD5C\uDB40\uDD54\uDB40\uDD11"
|
||||||
|
val HELLO_WORLD_WITH_EXTRAS = HELLO_WORLD + "askfasdf"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -55,6 +56,11 @@ class EmojiCoderTest {
|
|||||||
assertEquals("Hello, World!", EmojiCoder.decode(HELLO_WORLD))
|
assertEquals("Hello, World!", EmojiCoder.decode(HELLO_WORLD))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCrop() {
|
||||||
|
assertEquals(HELLO_WORLD, EmojiCoder.cropToFirstMessage(HELLO_WORLD_WITH_EXTRAS))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEncodeDecode() {
|
fun testEncodeDecode() {
|
||||||
for (emoji in EMOJI_LIST) {
|
for (emoji in EMOJI_LIST) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user