diff --git a/common/src/main/assets/lang/ar_AE.json b/common/src/main/assets/lang/ar_AE.json index 77c0e964..37e6fdb5 100644 --- a/common/src/main/assets/lang/ar_AE.json +++ b/common/src/main/assets/lang/ar_AE.json @@ -2570,7 +2570,8 @@ "platform_indicator": "يضيف أيقونة المنصة التي تم إرسال الوسيط منها (مثل Android، iOS، Web)", "location_indicator": "يضيف أيقونة \ud83d\udccd للـ snaps عندما يتم إرسالها مع تمكين الموقع", "ovf_editor_indicator": "يشير إلى ما إذا كان snap قد تم إرساله باستخدام محرر OVF", - "director_mode_indicator": "يضيف أيقونة \u270f\ufe0f للـ snaps عندما يتم إرسالها باستخدام وضع المخرج، والذي يمكن استخدامه لإرسال صور المعرض كـ snaps" + "director_mode_indicator": "يضيف أيقونة \u270f\ufe0f للـ snaps عندما يتم إرسالها باستخدام وضع المخرج، والذي يمكن استخدامه لإرسال صور المعرض كـ snaps", + "memories_indicator": "إضافة رمز \uD83D\uDCD6 للسنابات التي تم إعادة إرسالها من الذكريات بدلاً من التقاطها بالكاميرا الحية" }, "auto_mark_as_read": { "conversation_read": "وضع علامة مقروء على المحادثة عند إرسال رسالة", diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json index 7369cf61..95db4cf6 100644 --- a/common/src/main/assets/lang/en_US.json +++ b/common/src/main/assets/lang/en_US.json @@ -3233,7 +3233,8 @@ "platform_indicator": "Adds the platform icon from which a media was sent (e.g. Android, iOS, Web)", "location_indicator": "Adds a \ud83d\udccd icon to snaps when they have been sent with location enabled", "ovf_editor_indicator": "Indicates if a snap has been sent using OVF Editor", - "director_mode_indicator": "Adds a \u270f\ufe0f icon to snaps when they have been sent using Director Mode, which can be used to send gallery images as snaps" + "director_mode_indicator": "Adds a \u270f\ufe0f icon to snaps when they have been sent using Director Mode, which can be used to send gallery images as snaps", + "memories_indicator": "Adds a \ud83d\udcd6 icon to snaps that were re-sent from Memories instead of being captured with the live camera" }, "auto_mark_as_read": { "conversation_read": "Mark conversation as read when sending a message", diff --git a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/UserInterfaceTweaks.kt b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/UserInterfaceTweaks.kt index a07ec875..f6b2b1f8 100644 --- a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/UserInterfaceTweaks.kt +++ b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/UserInterfaceTweaks.kt @@ -56,7 +56,7 @@ class UserInterfaceTweaks : ConfigContainer() { val oldBitmojiSelfie = unique("old_bitmoji_selfie", "2d", "3d") { requireCleanCache() } val disableSpotlight = boolean("disable_spotlight") { requireRestart() } val verticalStoryViewer = boolean("vertical_story_viewer") { requireRestart() } - val messageIndicators = multiple("message_indicators", "encryption_indicator", "platform_indicator", "location_indicator", "ovf_editor_indicator", "director_mode_indicator") { requireRestart() } + val messageIndicators = multiple("message_indicators", "encryption_indicator", "platform_indicator", "location_indicator", "ovf_editor_indicator", "director_mode_indicator", "memories_indicator") { requireRestart() } val stealthModeIndicator = boolean("stealth_mode_indicator") { requireRestart() } val editTextOverride = multiple("edit_text_override", "multi_line_chat_input", "bypass_text_input_limit") { requireRestart(); addNotices(FeatureNotice.BAN_RISK, FeatureNotice.INTERNAL_BEHAVIOR) diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ui/MessageIndicators.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ui/MessageIndicators.kt index a78b3f9a..3bb9911f 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ui/MessageIndicators.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/ui/MessageIndicators.kt @@ -8,23 +8,69 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.Text -import androidx.compose.runtime.getValue +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.eternal.purrfectsnap.common.data.ContentType import me.eternal.purrfectsnap.common.ui.createComposeView -import me.eternal.purrfectsnap.common.ui.rememberAsyncMutableState import me.eternal.purrfectsnap.common.util.protobuf.ProtoReader import me.eternal.purrfectsnap.core.event.events.impl.BindViewEvent import me.eternal.purrfectsnap.core.features.Feature import me.eternal.purrfectsnap.core.ui.AppleLogo import kotlin.random.Random +@Composable +private fun GradientIcon( + imageVector: ImageVector, + brush: Brush, + size: androidx.compose.ui.unit.Dp = 16.dp +) { + Image( + imageVector = imageVector, + contentDescription = null, + colorFilter = ColorFilter.tint(Color.White), + modifier = Modifier + .size(size) + .graphicsLayer(alpha = 0.99f) + .drawWithContent { + drawContent() + drawRect(brush = brush, blendMode = BlendMode.SrcAtop) + } + ) +} + +@Composable +private fun GradientText( + text: String, + brush: Brush, + fontWeight: FontWeight, + fontSize: androidx.compose.ui.unit.TextUnit +) { + Text( + text = text, + color = Color.White, + fontWeight = fontWeight, + fontSize = fontSize, + modifier = Modifier + .graphicsLayer(alpha = 0.99f) + .drawWithContent { + drawContent() + drawRect(brush = brush, blendMode = BlendMode.SrcAtop) + } + ) +} + class MessageIndicators : Feature("Message Indicators") { override fun init() { val messageIndicatorsConfig = context.config.userInterface.messageIndicators.getNullable() ?: return @@ -35,96 +81,96 @@ class MessageIndicators : Feature("Message Indicators") { val appleLogo = AppleLogo context.event.subscribe(BindViewEvent::class) { event -> - event.chatMessage { _, _ -> + event.chatMessage { conversationId, _ -> val view = event.view as? ViewGroup ?: return@subscribe view.findViewWithTag(messageInfoTag)?.let { view.removeView(it) } val message = event.databaseMessage ?: return@chatMessage if (message.contentType != ContentType.SNAP.id && message.contentType != ContentType.EXTERNAL_MEDIA.id) return@chatMessage + if (message.senderId == context.database.myUserId) return@chatMessage val reader = ProtoReader(message.messageContent ?: return@chatMessage) + val isGroupConversation = (context.database.getConversationParticipants(conversationId)?.size ?: 0) > 2 createComposeView(event.view.context) { + val lockBrush = Brush.linearGradient(listOf(Color(0xFF4CD471), Color(0xFF00B8D9))) + val locationBrush = Brush.linearGradient(listOf(Color(0xFFFF4B5C), Color(0xFFFFB74D))) + val androidBrush = Brush.linearGradient(listOf(Color(0xFF3DDC84), Color(0xFFA5E635))) + val appleBrush = Brush.linearGradient(listOf(Color(0xFFFFFFFF), Color(0xFF94A3B8))) + val webBrush = Brush.linearGradient(listOf(Color(0xFF4FC3F7), Color(0xFF00E5FF))) + val directorBrush = Brush.linearGradient(listOf(Color(0xFFFFB74D), Color(0xFFFF5E3A))) + val ovfBrush = Brush.linearGradient(listOf(Color(0xFFFF5CA8), Color(0xFFA64CFF))) + val memoriesBrush = Brush.linearGradient(listOf(Color(0xFFFFE066), Color(0xFFFFB300))) + Box( modifier = Modifier .fillMaxWidth() .height(50.dp) - .padding(top = 4.dp, end = 1.dp), - contentAlignment = Alignment.TopEnd + .padding(top = 6.dp), + contentAlignment = Alignment.TopCenter ) { - val hasEncryption by rememberAsyncMutableState(defaultValue = false) { + val hasEncryption = remember(reader, isGroupConversation) { if (reader.containsPath(4, 4, 1, 1) || reader.containsPath(4, 4, 1, 1, 1) || reader.getByteArray(4, 3, 3) != null || reader.containsPath(3, 99, 3)) { - return@rememberAsyncMutableState true + return@remember true } - if (!reader.containsPath(3, 99, 5, 1)) return@rememberAsyncMutableState false - reader.containsPath(4, 5, 1, 3, 1) - || reader.getVarInt(4, 5, 1, 3, 2, 9) == 1L + if (isGroupConversation) return@remember false + if (reader.containsPath(4, 5, 1, 3, 1)) return@remember true + reader.getVarInt(4, 5, 1, 3, 2, 9) in setOf(1L, 3L) } - val sentFromIosDevice by rememberAsyncMutableState(defaultValue = false) { + val sentFromIosDevice = remember(reader) { if (reader.containsPath(4, 4, 3)) !reader.containsPath(4, 4, 3, 3, 17) else reader.getVarInt(4, 4, 11, 17, 7) != null } - val sentFromWebApp by rememberAsyncMutableState(defaultValue = false) { + val sentFromWebApp = remember(reader) { reader.getVarInt(4, 4, *(if (reader.containsPath(4, 4, 3)) intArrayOf(3, 3, 22, 1) else intArrayOf(11, 22, 1))) == 7L } - val sentWithLocation by rememberAsyncMutableState(defaultValue = false) { + val sentWithLocation = remember(reader) { reader.getVarInt(4, 4, 11, 17, 5) != null } - val sentUsingOvfEditor by rememberAsyncMutableState(defaultValue = false) { + val sentUsingOvfEditor = remember(reader) { (reader.getString(4, 4, 11, 12, 1) ?: reader.getString(4, 4, 11, 13, 4, 1, 2, 12, 20, 1)) == "c13129f7-fe4a-44c4-9b9d-e0b26fee8f82" } - val sentUsingDirectorMode by rememberAsyncMutableState(defaultValue = false) { + val sentUsingDirectorMode = remember(reader) { reader.followPath(4, 4, 11, 28)?.let { (it.getVarInt(1) to it.getVarInt(2)) == (0L to 0L) } == true || reader.getByteArray(4, 4, 11, 13, 4, 1, 2, 12, 27, 1) != null } + val sentFromMemories = remember(reader) { + reader.getVarInt(4, 18) != null + || reader.getString(4, 5, 1, 2)?.contains("/h/") == true + } Row( - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) ) { if (sentWithLocation && messageIndicatorsConfig.contains("location_indicator")) { - Image( - imageVector = Icons.Default.LocationOn, - colorFilter = ColorFilter.tint(Color.Green), - contentDescription = null, - modifier = Modifier.size(15.dp) - ) + GradientIcon(Icons.Default.LocationOn, locationBrush) } if (messageIndicatorsConfig.contains("platform_indicator")) { - Image( - imageVector = when { - sentFromWebApp -> Icons.Default.Laptop - sentFromIosDevice -> appleLogo - else -> Icons.Default.Android - }, - colorFilter = ColorFilter.tint(Color.Green), - contentDescription = null, - modifier = Modifier.size(15.dp) - ) + val (platformIcon, platformBrush) = when { + sentFromWebApp -> Icons.Default.Laptop to webBrush + sentFromIosDevice -> appleLogo to appleBrush + else -> Icons.Default.Android to androidBrush + } + GradientIcon(platformIcon, platformBrush) } if (hasEncryption && messageIndicatorsConfig.contains("encryption_indicator")) { - Image( - imageVector = Icons.Default.Lock, - colorFilter = ColorFilter.tint(Color.Green), - contentDescription = null, - modifier = Modifier.size(15.dp) - ) + GradientIcon(Icons.Default.Lock, lockBrush) } if (sentUsingDirectorMode && messageIndicatorsConfig.contains("director_mode_indicator")) { - Image( - imageVector = Icons.Default.Edit, - colorFilter = ColorFilter.tint(Color.Red), - contentDescription = null, - modifier = Modifier.size(15.dp) - ) + GradientIcon(Icons.Default.Edit, directorBrush) + } + if (sentFromMemories && messageIndicatorsConfig.contains("memories_indicator")) { + GradientIcon(Icons.Default.HistoryEdu, memoriesBrush) } if (sentUsingOvfEditor && messageIndicatorsConfig.contains("ovf_editor_indicator")) { - Text( + GradientText( text = "OVF", - color = Color.Red, + brush = ovfBrush, fontWeight = FontWeight.ExtraBold, - fontSize = 10.sp, + fontSize = 11.sp ) } }