mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-11 20:04:32 +02:00
refactor(manager/social): messaging task
This commit is contained in:
parent
50a43ee6ad
commit
9f098834cf
@ -0,0 +1,125 @@
|
|||||||
|
package me.rhunk.snapenhance.messaging
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableIntState
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
|
||||||
|
import me.rhunk.snapenhance.bridge.snapclient.types.Message
|
||||||
|
import me.rhunk.snapenhance.common.data.ContentType
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
|
enum class MessagingTaskType(
|
||||||
|
val key: String
|
||||||
|
) {
|
||||||
|
SAVE("SAVE"),
|
||||||
|
UNSAVE("UNSAVE"),
|
||||||
|
DELETE("ERASE"),
|
||||||
|
READ("READ"),
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias MessagingTaskConstraint = Message.() -> Boolean
|
||||||
|
|
||||||
|
object MessagingConstraints {
|
||||||
|
val USER_ID: (String) -> MessagingTaskConstraint = { userId: String ->
|
||||||
|
{
|
||||||
|
this.senderId == userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val NO_USER_ID: (String) -> MessagingTaskConstraint = { userId: String ->
|
||||||
|
{
|
||||||
|
this.senderId != userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val MY_USER_ID: (messagingBridge: MessagingBridge) -> MessagingTaskConstraint = {
|
||||||
|
val myUserId = it.myUserId
|
||||||
|
{
|
||||||
|
this.senderId == myUserId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val CONTENT_TYPE: (ContentType) -> MessagingTaskConstraint = { contentType: ContentType ->
|
||||||
|
{
|
||||||
|
this.contentType == contentType.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessagingTask(
|
||||||
|
private val messagingBridge: MessagingBridge,
|
||||||
|
private val conversationId: String,
|
||||||
|
private val taskType: MessagingTaskType,
|
||||||
|
private val constraints: List<MessagingTaskConstraint>,
|
||||||
|
private val processedMessageCount: MutableIntState,
|
||||||
|
private val onSuccess: (message: Message) -> Unit = {},
|
||||||
|
private val onFailure: (message: Message, reason: String) -> Unit = { _, _ -> },
|
||||||
|
private val overrideClientMessageIds: List<Long>? = null,
|
||||||
|
private val amountToProcess: Int? = null,
|
||||||
|
) {
|
||||||
|
private suspend fun processMessages(
|
||||||
|
messages: List<Message>
|
||||||
|
) {
|
||||||
|
messages.forEach { message ->
|
||||||
|
if (constraints.any { !it(message) }) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = messagingBridge.updateMessage(conversationId, message.clientMessageId, taskType.key)
|
||||||
|
error?.takeIf { error != "DUPLICATE_REQUEST" }?.let {
|
||||||
|
onFailure(message, error)
|
||||||
|
}
|
||||||
|
onSuccess(message)
|
||||||
|
processedMessageCount.intValue++
|
||||||
|
delay(Random.nextLong(50, 170))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasFixedGoal() = overrideClientMessageIds?.takeIf { it.isNotEmpty() } != null || amountToProcess?.takeIf { it > 0 } != null
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
var processedOverrideMessages = 0
|
||||||
|
var lastMessageId = Long.MAX_VALUE
|
||||||
|
|
||||||
|
do {
|
||||||
|
val fetchedMessages = messagingBridge.fetchConversationWithMessagesPaginated(
|
||||||
|
conversationId,
|
||||||
|
100,
|
||||||
|
lastMessageId
|
||||||
|
) ?: return
|
||||||
|
|
||||||
|
if (fetchedMessages.isEmpty()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMessageId = fetchedMessages.first().clientMessageId
|
||||||
|
|
||||||
|
overrideClientMessageIds?.let { ids ->
|
||||||
|
fetchedMessages.retainAll { message ->
|
||||||
|
ids.contains(message.clientMessageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amountToProcess?.let { amount ->
|
||||||
|
while (processedMessageCount.intValue + fetchedMessages.size > amount) {
|
||||||
|
fetchedMessages.removeLastOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processMessages(fetchedMessages.reversed())
|
||||||
|
|
||||||
|
overrideClientMessageIds?.let { ids ->
|
||||||
|
processedOverrideMessages += fetchedMessages.count { message ->
|
||||||
|
ids.contains(message.clientMessageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processedOverrideMessages >= ids.size) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amountToProcess?.let { amount ->
|
||||||
|
if (processedMessageCount.intValue >= amount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,25 @@
|
|||||||
package me.rhunk.snapenhance.ui.manager.sections.social
|
package me.rhunk.snapenhance.ui.manager.sections.social
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.DeleteForever
|
import androidx.compose.material.icons.rounded.BookmarkAdded
|
||||||
|
import androidx.compose.material.icons.rounded.BookmarkBorder
|
||||||
|
import androidx.compose.material.icons.rounded.DeleteForever
|
||||||
|
import androidx.compose.material.icons.rounded.RemoveRedEye
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -23,71 +30,121 @@ import me.rhunk.snapenhance.common.data.ContentType
|
|||||||
import me.rhunk.snapenhance.common.data.SocialScope
|
import me.rhunk.snapenhance.common.data.SocialScope
|
||||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||||
import me.rhunk.snapenhance.common.util.snap.SnapWidgetBroadcastReceiverHelper
|
import me.rhunk.snapenhance.common.util.snap.SnapWidgetBroadcastReceiverHelper
|
||||||
import me.rhunk.snapenhance.ui.util.AlertDialogs
|
import me.rhunk.snapenhance.messaging.MessagingConstraints
|
||||||
|
import me.rhunk.snapenhance.messaging.MessagingTask
|
||||||
|
import me.rhunk.snapenhance.messaging.MessagingTaskConstraint
|
||||||
|
import me.rhunk.snapenhance.messaging.MessagingTaskType
|
||||||
import me.rhunk.snapenhance.ui.util.Dialog
|
import me.rhunk.snapenhance.ui.util.Dialog
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
class MessagingPreview(
|
class MessagingPreview(
|
||||||
private val context: RemoteSideContext,
|
private val context: RemoteSideContext,
|
||||||
private val scope: SocialScope,
|
private val scope: SocialScope,
|
||||||
private val scopeId: String
|
private val scopeId: String
|
||||||
) {
|
) {
|
||||||
private val alertDialogs by lazy { AlertDialogs(context.translation) }
|
|
||||||
|
|
||||||
private lateinit var coroutineScope: CoroutineScope
|
private lateinit var coroutineScope: CoroutineScope
|
||||||
private lateinit var messagingBridge: MessagingBridge
|
private lateinit var messagingBridge: MessagingBridge
|
||||||
private lateinit var previewScrollState: LazyListState
|
private lateinit var previewScrollState: LazyListState
|
||||||
private val myUserId by lazy { messagingBridge.myUserId }
|
private val myUserId by lazy { messagingBridge.myUserId }
|
||||||
|
|
||||||
private var conversationId: String? = null
|
private var conversationId: String? = null
|
||||||
private val messages = sortedMapOf<Long, Message>()
|
private val messages = sortedMapOf<Long, Message>() // server message id => message
|
||||||
private var messageSize by mutableIntStateOf(0)
|
private var messageSize by mutableIntStateOf(0)
|
||||||
private var lastMessageId = Long.MAX_VALUE
|
private var lastMessageId = Long.MAX_VALUE
|
||||||
private val selectedMessages = mutableStateListOf<Long>()
|
private val selectedMessages = mutableStateListOf<Long>() // client message id
|
||||||
|
|
||||||
private fun toggleSelectedMessage(messageId: Long) {
|
private fun toggleSelectedMessage(messageId: Long) {
|
||||||
if (selectedMessages.contains(messageId)) selectedMessages.remove(messageId)
|
if (selectedMessages.contains(messageId)) selectedMessages.remove(messageId)
|
||||||
else selectedMessages.add(messageId)
|
else selectedMessages.add(messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ActionButton(
|
||||||
|
text: String,
|
||||||
|
icon: ImageVector,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = onClick,
|
||||||
|
text = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(5.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(text = text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TopBarAction() {
|
fun TopBarAction() {
|
||||||
var deletedMessageCount by remember { mutableIntStateOf(0) }
|
var showDropDown by remember { mutableStateOf(false) }
|
||||||
var messageDeleteJob by remember { mutableStateOf(null as Job?) }
|
var activeTask by remember { mutableStateOf(null as MessagingTask?) }
|
||||||
|
var activeJob by remember { mutableStateOf(null as Job?) }
|
||||||
|
val processMessageCount = remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
fun deleteIndividualMessage(serverMessageId: Long) {
|
fun triggerMessagingTask(taskType: MessagingTaskType, constraints: List<MessagingTaskConstraint> = listOf(), onSuccess: (Message) -> Unit = {}) {
|
||||||
val message = messages[serverMessageId] ?: return
|
showDropDown = false
|
||||||
if (message.senderId != myUserId) return
|
processMessageCount.intValue = 0
|
||||||
|
activeTask = MessagingTask(
|
||||||
val error = messagingBridge.updateMessage(conversationId, message.clientMessageId, "ERASE")
|
messagingBridge = messagingBridge,
|
||||||
|
conversationId = conversationId!!,
|
||||||
if (error != null) {
|
taskType = taskType,
|
||||||
context.shortToast("Failed to delete message: $error")
|
constraints = constraints,
|
||||||
} else {
|
overrideClientMessageIds = selectedMessages.takeIf { it.isNotEmpty() }?.toList(),
|
||||||
coroutineScope.launch {
|
processedMessageCount = processMessageCount,
|
||||||
deletedMessageCount++
|
onFailure = { message, reason ->
|
||||||
messages.remove(serverMessageId)
|
context.log.verbose("Failed to process message ${message.clientMessageId}: $reason")
|
||||||
messageSize = messages.size
|
}
|
||||||
|
)
|
||||||
|
selectedMessages.clear()
|
||||||
|
activeJob = coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
activeTask?.run()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
activeTask = null
|
||||||
|
activeJob = null
|
||||||
|
}
|
||||||
|
}.also { job ->
|
||||||
|
job.invokeOnCompletion {
|
||||||
|
if (it != null) {
|
||||||
|
context.log.verbose("Failed to process messages: ${it.message}")
|
||||||
|
return@invokeOnCompletion
|
||||||
|
}
|
||||||
|
context.longToast("Processed ${processMessageCount.intValue} messages")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageDeleteJob != null) {
|
if (activeJob != null) {
|
||||||
Dialog(onDismissRequest = {
|
Dialog(onDismissRequest = {
|
||||||
messageDeleteJob?.cancel()
|
activeJob?.cancel()
|
||||||
messageDeleteJob = null
|
activeJob = null
|
||||||
|
activeTask = null
|
||||||
}) {
|
}) {
|
||||||
Card {
|
Column(
|
||||||
Column(
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxWidth()
|
||||||
.padding(20.dp)
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
.fillMaxWidth(),
|
.border(1.dp, MaterialTheme.colorScheme.onSurface, RoundedCornerShape(20.dp))
|
||||||
verticalArrangement = Arrangement.Center,
|
.padding(15.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
verticalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
Text("Deleting messages ($deletedMessageCount)")
|
) {
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Text("Processed ${processMessageCount.intValue} messages")
|
||||||
|
if (activeTask?.hasFixedGoal() == true) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(5.dp),
|
||||||
|
progress = processMessageCount.intValue.toFloat() / selectedMessages.size.toFloat(),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
} else {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding()
|
.padding()
|
||||||
@ -100,93 +157,51 @@ class MessagingPreview(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = { showDropDown = !showDropDown }) {
|
||||||
|
Icon(imageVector = Icons.Filled.MoreVert, contentDescription = null)
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedMessages.isNotEmpty()) {
|
if (selectedMessages.isNotEmpty()) {
|
||||||
IconButton(onClick = {
|
|
||||||
deletedMessageCount = 0
|
|
||||||
messageDeleteJob = coroutineScope.launch(Dispatchers.IO) {
|
|
||||||
selectedMessages.toList().also {
|
|
||||||
selectedMessages.clear()
|
|
||||||
}.forEach { messageId ->
|
|
||||||
deleteIndividualMessage(messageId)
|
|
||||||
}
|
|
||||||
}.apply {
|
|
||||||
invokeOnCompletion {
|
|
||||||
context.shortToast("Successfully deleted $deletedMessageCount messages")
|
|
||||||
messageDeleteJob = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Delete,
|
|
||||||
contentDescription = "Delete"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
selectedMessages.clear()
|
selectedMessages.clear()
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.Close, contentDescription = "Close")
|
||||||
imageVector = Icons.Filled.Close,
|
|
||||||
contentDescription = "Close"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
var deleteAllConfirmationDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (deleteAllConfirmationDialog) {
|
MaterialTheme(
|
||||||
Dialog(onDismissRequest = { deleteAllConfirmationDialog = false }) {
|
colorScheme = MaterialTheme.colorScheme.copy(
|
||||||
alertDialogs.ConfirmDialog(
|
surface = MaterialTheme.colorScheme.inverseSurface,
|
||||||
title = "Are you sure you want to delete all your messages?",
|
onSurface = MaterialTheme.colorScheme.inverseOnSurface
|
||||||
message = "Warning: This action may flag your account for spam if used excessively.",
|
),
|
||||||
onDismiss = {
|
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(50.dp))
|
||||||
deleteAllConfirmationDialog = false
|
) {
|
||||||
}, onConfirm = {
|
DropdownMenu(
|
||||||
deletedMessageCount = 0
|
expanded = showDropDown, onDismissRequest = {
|
||||||
deleteAllConfirmationDialog = false
|
showDropDown = false
|
||||||
messageDeleteJob = coroutineScope.launch(Dispatchers.IO) {
|
}
|
||||||
var lastMessageId = Long.MAX_VALUE
|
) {
|
||||||
|
val hasSelection = selectedMessages.isNotEmpty()
|
||||||
do {
|
ActionButton(text = if (hasSelection) "Save selection" else "Save all", icon = Icons.Rounded.BookmarkAdded) {
|
||||||
val fetchedMessages = messagingBridge.fetchConversationWithMessagesPaginated(
|
triggerMessagingTask(MessagingTaskType.SAVE)
|
||||||
conversationId!!,
|
}
|
||||||
100,
|
ActionButton(text = if (hasSelection) "Unsave selection" else "Unsave all", icon = Icons.Rounded.BookmarkBorder) {
|
||||||
lastMessageId
|
triggerMessagingTask(MessagingTaskType.UNSAVE)
|
||||||
)
|
}
|
||||||
|
ActionButton(text = if (hasSelection) "Mark selected Snap as seen" else "Mark all Snaps as seen", icon = Icons.Rounded.RemoveRedEye) {
|
||||||
if (fetchedMessages == null) {
|
triggerMessagingTask(MessagingTaskType.READ, listOf(
|
||||||
context.shortToast("Failed to fetch messages")
|
MessagingConstraints.NO_USER_ID(myUserId),
|
||||||
return@launch
|
MessagingConstraints.CONTENT_TYPE(ContentType.SNAP)
|
||||||
}
|
))
|
||||||
|
}
|
||||||
if (fetchedMessages.isEmpty()) {
|
ActionButton(text = if (hasSelection) "Delete selected" else "Delete all", icon = Icons.Rounded.DeleteForever) {
|
||||||
break
|
triggerMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(myUserId))) { message ->
|
||||||
}
|
coroutineScope.launch {
|
||||||
|
messages.remove(message.serverMessageId)
|
||||||
fetchedMessages.forEach {
|
messageSize = messages.size
|
||||||
deleteIndividualMessage(it.serverMessageId)
|
}
|
||||||
delay(Random.nextLong(50, 170))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
lastMessageId = fetchedMessages.first().clientMessageId
|
|
||||||
} while (true)
|
|
||||||
}.apply {
|
|
||||||
invokeOnCompletion {
|
|
||||||
messageDeleteJob = null
|
|
||||||
context.shortToast("Successfully deleted $deletedMessageCount messages")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = {
|
|
||||||
deleteAllConfirmationDialog = true
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.DeleteForever,
|
|
||||||
contentDescription = "Delete"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,7 +239,7 @@ class MessagingPreview(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(messageSize) {index ->
|
items(messageSize) {index ->
|
||||||
val elementKey = remember(index) { messages.entries.elementAt(index).key }
|
val elementKey = remember(index) { messages.entries.elementAt(index).value.clientMessageId }
|
||||||
val messageReader = ProtoReader(messages.entries.elementAt(index).value.content)
|
val messageReader = ProtoReader(messages.entries.elementAt(index).value.content)
|
||||||
val contentType = ContentType.fromMessageContainer(messageReader)
|
val contentType = ContentType.fromMessageContainer(messageReader)
|
||||||
|
|
||||||
|
@ -237,9 +237,7 @@ class SocialSection : Section() {
|
|||||||
fontWeight = FontWeight.Light
|
fontWeight = FontWeight.Light
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
if (streaks != null && streaks.notify) {
|
if (streaks != null && streaks.notify) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = ImageVector.vectorResource(id = R.drawable.streak_icon),
|
imageVector = ImageVector.vectorResource(id = R.drawable.streak_icon),
|
||||||
@ -268,10 +266,7 @@ class SocialSection : Section() {
|
|||||||
MESSAGING_PREVIEW_ROUTE.replace("{id}", id).replace("{scope}", scope.key)
|
MESSAGING_PREVIEW_ROUTE.replace("{id}", id).replace("{scope}", scope.key)
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(imageVector = Icons.Filled.RemoveRedEye, contentDescription = null)
|
||||||
imageVector = Icons.Filled.RemoveRedEye,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,17 +11,25 @@ import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
|||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||||
|
|
||||||
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
||||||
lateinit var conversationManager: Any
|
private var _conversationManager: Any? = null
|
||||||
|
val conversationManager: Any
|
||||||
|
get() = _conversationManager ?: throw IllegalStateException("ConversationManager is not initialized").also {
|
||||||
|
context.longToast("Failed to get conversation manager. Please restart Snapchat")
|
||||||
|
}
|
||||||
|
|
||||||
var openedConversationUUID: SnapUUID? = null
|
var openedConversationUUID: SnapUUID? = null
|
||||||
|
private set
|
||||||
var lastFetchConversationUserUUID: SnapUUID? = null
|
var lastFetchConversationUserUUID: SnapUUID? = null
|
||||||
|
private set
|
||||||
var lastFetchConversationUUID: SnapUUID? = null
|
var lastFetchConversationUUID: SnapUUID? = null
|
||||||
|
private set
|
||||||
var lastFetchGroupConversationUUID: SnapUUID? = null
|
var lastFetchGroupConversationUUID: SnapUUID? = null
|
||||||
var lastFocusedMessageId: Long = -1
|
var lastFocusedMessageId: Long = -1
|
||||||
|
private set
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
Hooker.hookConstructor(context.classCache.conversationManager, HookStage.BEFORE) {
|
Hooker.hookConstructor(context.classCache.conversationManager, HookStage.BEFORE) {
|
||||||
conversationManager = it.thisObject()
|
_conversationManager = it.thisObject()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user