Skip to content

Commit

Permalink
feat(core): better send override
Browse files Browse the repository at this point in the history
- compose ui
- fix "Media Upload" label
  • Loading branch information
rhunk committed Aug 19, 2024
1 parent a43e404 commit a877c04
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 51 deletions.
8 changes: 6 additions & 2 deletions common/src/main/assets/lang/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1524,7 +1524,8 @@
"negative": "No",
"cancel": "Cancel",
"open": "Open",
"download": "Download"
"download": "Download",
"send": "Send"
},

"better_notifications": {
Expand Down Expand Up @@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
}
}
Expand All @@ -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()
Expand All @@ -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"]
Expand All @@ -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" -> {
Expand All @@ -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()
}
}
}
Expand Down

0 comments on commit a877c04

Please sign in to comment.