commit a877c04461f07e282575ed288e6663b356d14b80
parent a43e4049a3998f618c03389196299b61e2f5865d
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Tue, 20 Aug 2024 00:40:26 +0200
feat(core): better send override
- compose ui
- fix "Media Upload" label
Diffstat:
2 files changed, 221 insertions(+), 51 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -1524,7 +1524,8 @@
"negative": "No",
"cancel": "Cancel",
"open": "Open",
- "download": "Download"
+ "download": "Download",
+ "send": "Send"
},
"better_notifications": {
@@ -1698,6 +1699,9 @@
"sigColorStoryRingDiscoverTabThumbnailStoryRing": "Story Ring Discover Tab Thumbnail Story Ring Color"
},
"send_override_dialog": {
- "title": "Send media as ..."
+ "title": "Send media as {type}",
+ "duration": "Duration: {duration}",
+ "saveable_snap_hint": "Make Snap saveable in the chat",
+ "unlimited_duration": "Unlimited"
}
}
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/SendOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/SendOverride.kt
@@ -1,8 +1,23 @@
package me.rhunk.snapenhance.core.features.impl.messaging
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.MusicNote
+import androidx.compose.material.icons.filled.Photo
+import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import me.rhunk.snapenhance.common.data.ContentType
+import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.common.util.protobuf.ProtoWriter
@@ -12,19 +27,17 @@ import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent
import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.features.impl.experiments.MediaFilePicker
import me.rhunk.snapenhance.core.messaging.MessageSender
-import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
-import me.rhunk.snapenhance.nativelib.NativeLib
+import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull
import java.util.Locale
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
class SendOverride : Feature("Send Override") {
- private val typeNames by lazy {
- mutableListOf("ORIGINAL", "SNAP", "NOTE").also {
- if (NativeLib.initialized) {
- it.add("SAVEABLE_SNAP")
- }
- }.associateWith { it }
- }
+ private var selectedType by mutableStateOf("SNAP")
+ private var customDuration by mutableStateOf(10f)
+ @OptIn(ExperimentalLayoutApi::class)
override fun init() {
val stripSnapMetadata = context.config.messaging.stripMediaMetadata.get()
var postSavePolicy: Int? = null
@@ -83,19 +96,28 @@ class SendOverride : Feature("Send Override") {
event.onMediaUploaded { result ->
result.messageContent.content = ProtoEditor(result.messageContent.content!!).apply {
edit(11, 5) {
- // remove media upload hint when viewing snap
edit(1) {
edit(1) {
+ snapDocPlayback.getVarInt(2, 99)?.let { customDuration ->
+ remove(15)
+ addVarInt(15, customDuration)
+ }
remove(27)
+ remove(26)
addBuffer(26, byteArrayOf())
}
}
+ // set back the original snap duration
remove(2)
snapDocPlayback.getByteArray(2)?.let {
addBuffer(2, it)
}
}
+
+ edit(11, 5, 2) {
+ remove(99)
+ }
}.toByteArray()
}
}
@@ -104,17 +126,17 @@ class SendOverride : Feature("Send Override") {
context.event.subscribe(NativeUnaryCallEvent::class) { event ->
if (event.uri != "/messagingcoreservice.MessagingCoreService/CreateContentMessage") return@subscribe
postSavePolicy?.let { savePolicy ->
- context.log.verbose("post save policy $savePolicy")
+ context.log.verbose("postSavePolicy=$savePolicy")
event.buffer = ProtoEditor(event.buffer).apply {
- edit {
- edit(4) {
- remove(7)
- addVarInt(7, savePolicy)
- }
- add(6) {
- from(9) {
- addVarInt(1, 1)
- }
+ edit(4) {
+ remove(7)
+ addVarInt(7, savePolicy)
+ }
+
+ // remove Keep Snaps in Chat ability
+ if (savePolicy == 1/* PROHIBITED */) {
+ edit(6, 9) {
+ remove(1)
}
}
}.toByteArray()
@@ -125,16 +147,16 @@ class SendOverride : Feature("Send Override") {
postSavePolicy = null
if (event.destinations.stories?.isNotEmpty() == true && event.destinations.conversations?.isEmpty() == true) return@subscribe
val localMessageContent = event.messageContent
- if (localMessageContent.contentType != ContentType.EXTERNAL_MEDIA) return@subscribe
-
//prevent story replies
- val messageProtoReader = ProtoReader(localMessageContent.content!!)
+ if (localMessageContent.contentType != ContentType.EXTERNAL_MEDIA && localMessageContent.instanceNonNull().getObjectFieldOrNull("mExternalContentMetadata")?.getObjectFieldOrNull("mContainsExternalContent") != true) return@subscribe
+
+ val messageProtoReader = ProtoReader(localMessageContent.content ?: return@subscribe)
if (messageProtoReader.contains(7)) return@subscribe
event.canceled = true
- fun sendMedia(overrideType: String): Boolean {
- if (overrideType != "ORIGINAL" && messageProtoReader.followPath(3)?.getCount(3) != 1) {
+ fun sendMedia(overrideType: String, snapDurationMs: Int?): Boolean {
+ if (overrideType != "ORIGINAL" && (messageProtoReader.followPath(3)?.getCount(3) ?: 0) > 1) {
context.inAppOverlay.showStatusToast(
icon = Icons.Default.WarningAmber,
context.translation["gallery_media_send_override.multiple_media_toast"]
@@ -147,26 +169,49 @@ class SendOverride : Feature("Send Override") {
postSavePolicy = if (overrideType == "SAVEABLE_SNAP") 3 /* VIEW_SESSION */ else 1 /* PROHIBITED */
val extras = messageProtoReader.followPath(3, 3, 13)?.getBuffer()
- localMessageContent.contentType = ContentType.SNAP
- localMessageContent.content = ProtoWriter().apply {
- from(11) {
- from(5) {
- from(1) {
+
+ if (localMessageContent.contentType != ContentType.SNAP) {
+ localMessageContent.content = ProtoWriter().apply {
+ from(11) {
+ from(5) {
from(1) {
- addVarInt(2, 0)
- addVarInt(12, 0)
- addVarInt(15, 0)
+ from(1) {
+ addVarInt(2, 0)
+ addVarInt(12, 0)
+ addVarInt(15, 0)
+ }
+ addVarInt(6, 0)
}
- addVarInt(6, 0)
+ from(2) {}
}
- messageProtoReader.getByteArray(3, 3, 5, 2)?.let {
- addBuffer(2, it)
+ extras?.let {
+ addBuffer(13, it)
}
+ from(22) {}
}
- extras?.let {
- addBuffer(13, it)
+ }.toByteArray()
+ }
+
+ localMessageContent.contentType = ContentType.SNAP
+ localMessageContent.content = ProtoEditor(localMessageContent.content!!).apply {
+ edit(11, 5, 2) {
+ arrayOf(6, 7, 8).forEach { remove(it) }
+ // set snap duration
+ if (snapDurationMs != null) {
+ addVarInt(8, snapDurationMs / 1000)
+ if (snapDurationMs / 1000 <= 0) {
+ addVarInt(99, snapDurationMs)
+ }
+ } else {
+ addBuffer(6, byteArrayOf())
}
}
+
+ // set app source
+ edit(11, 22) {
+ remove(4)
+ addVarInt(4, 5) // APP_SOURCE_CAMERA
+ }
}.toByteArray()
}
"NOTE" -> {
@@ -183,25 +228,146 @@ class SendOverride : Feature("Send Override") {
}
if (configOverrideType != "always_ask") {
- if (sendMedia(configOverrideType)) {
+ if (sendMedia(configOverrideType, 10)) {
event.invokeOriginal()
}
return@subscribe
}
context.runOnUiThread {
- ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity!!)
- .setItems(typeNames.values.map {
- context.translation["features.options.gallery_media_send_override.$it"]
- }.toTypedArray()) { dialog, which ->
- dialog.dismiss()
- if (sendMedia(typeNames.keys.toTypedArray()[which])) {
- event.invokeOriginal()
+ createComposeAlertDialog(context.mainActivity!!) { alertDialog ->
+ val mainTranslation = remember {
+ context.translation.getCategory("send_override_dialog")
+ }
+
+ @Composable
+ fun ActionTile(
+ modifier: Modifier = Modifier,
+ selected: Boolean = false,
+ icon: ImageVector,
+ title: String,
+ onClick: () -> Unit
+ ) {
+ Card(
+ modifier = modifier,
+ onClick = onClick,
+ elevation = if (selected) CardDefaults.elevatedCardElevation(disabledElevation = 3.dp) else CardDefaults.cardElevation(),
+ colors = if (selected) CardDefaults.elevatedCardColors() else CardDefaults.cardColors()
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .size(75.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Icon(icon, contentDescription = title, modifier = Modifier
+ .size(32.dp)
+ .padding(4.dp))
+ Text(title, modifier = Modifier.fillMaxWidth(), fontSize = 12.sp, fontWeight = FontWeight.Light, softWrap = true, lineHeight = 14.sp, textAlign = TextAlign.Center)
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ val translation = remember {
+ context.translation.getCategory("features.options.gallery_media_send_override")
+ }
+
+ Text(fontSize = 20.sp, fontWeight = FontWeight.Medium, text = "Send as ${
+ translation[selectedType]}", modifier = Modifier.padding(5.dp))
+ FlowRow(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceEvenly
+ ) {
+ ActionTile(selected = selectedType == "ORIGINAL", icon = Icons.Filled.Photo, title =
+ translation["ORIGINAL"]) {
+ selectedType = "ORIGINAL"
+ }
+ ActionTile(selected = selectedType == "SNAP" || selectedType == "SAVEABLE_SNAP", icon = Icons.Filled.PhotoCamera, title = translation["SNAP"]) {
+ selectedType = "SNAP"
+ }
+ ActionTile(selected = selectedType == "NOTE", icon = Icons.Filled.MusicNote, title = translation["NOTE"]) {
+ selectedType = "NOTE"
+ }
+ }
+
+ fun convertDuration(duration: Float): Int? {
+ return when {
+ duration in -2f..-1f -> 100
+ duration in -1f..-0f -> 250
+ duration in -0f..1f -> 500
+ duration >= 11f -> null
+ else -> ((duration * 1000).toInt() / 1000) * 1000
+ }
+ }
+
+ when (selectedType) {
+ "SNAP", "SAVEABLE_SNAP" -> {
+ fun toggleSaveable() {
+ selectedType = if (selectedType == "SAVEABLE_SNAP") "SNAP" else "SAVEABLE_SNAP"
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth().clickable {
+ toggleSaveable()
+ },
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ){
+ Checkbox(
+ checked = selectedType == "SAVEABLE_SNAP",
+ onCheckedChange = {
+ toggleSaveable()
+ }
+ )
+ Text(text = mainTranslation["saveable_snap_hint"], lineHeight = 15.sp)
+ }
+ Column(
+ modifier = Modifier.padding(start = 8.dp)
+ ) {
+ Text(
+ text = mainTranslation.format("duration",
+ "duration" to (convertDuration(customDuration)?.toDuration(DurationUnit.MILLISECONDS)?.toString(DurationUnit.SECONDS, 2) ?: mainTranslation["unlimited_duration"])
+ )
+ )
+ Slider(
+ modifier = Modifier.fillMaxWidth(),
+ value = customDuration,
+ onValueChange = {
+ customDuration = it
+ },
+ valueRange = -2f..11f,
+ )
+ }
+ }
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ OutlinedButton(onClick = {
+ alertDialog.dismiss()
+ }) {
+ Text(context.translation["button.cancel"])
+ }
+ Button(onClick = {
+ alertDialog.dismiss()
+ if (sendMedia(selectedType, convertDuration(customDuration))) {
+ event.invokeOriginal()
+ }
+ }) {
+ Text(context.translation["button.send"])
+ }
}
}
- .setTitle(context.translation["send_override_dialog.title"])
- .setNegativeButton(context.translation["button.cancel"], null)
- .show()
+ }.show()
}
}
}