mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-06 17:34:32 +02:00
feat(core): bulk clean conversations
This commit is contained in:
parent
b92378bffd
commit
7fc3ec9d10
@ -33,12 +33,12 @@ import me.rhunk.snapenhance.common.Constants
|
|||||||
import me.rhunk.snapenhance.common.ReceiversConfig
|
import me.rhunk.snapenhance.common.ReceiversConfig
|
||||||
import me.rhunk.snapenhance.common.data.ContentType
|
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.messaging.MessagingConstraints
|
||||||
|
import me.rhunk.snapenhance.common.messaging.MessagingTask
|
||||||
|
import me.rhunk.snapenhance.common.messaging.MessagingTaskConstraint
|
||||||
|
import me.rhunk.snapenhance.common.messaging.MessagingTaskType
|
||||||
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.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.manager.Routes
|
import me.rhunk.snapenhance.ui.manager.Routes
|
||||||
import me.rhunk.snapenhance.ui.util.Dialog
|
import me.rhunk.snapenhance.ui.util.Dialog
|
||||||
|
|
||||||
@ -299,14 +299,17 @@ class MessagingPreview: Routes.Route() {
|
|||||||
else selectConstraintsDialog = true
|
else selectConstraintsDialog = true
|
||||||
}
|
}
|
||||||
ActionButton(text = translation[if (hasSelection) "mark_selection_as_seen_option" else "mark_all_as_seen_option"], icon = Icons.Rounded.RemoveRedEye) {
|
ActionButton(text = translation[if (hasSelection) "mark_selection_as_seen_option" else "mark_all_as_seen_option"], icon = Icons.Rounded.RemoveRedEye) {
|
||||||
launchMessagingTask(MessagingTaskType.READ, listOf(
|
launchMessagingTask(
|
||||||
|
MessagingTaskType.READ, listOf(
|
||||||
MessagingConstraints.NO_USER_ID(messagingBridge.myUserId),
|
MessagingConstraints.NO_USER_ID(messagingBridge.myUserId),
|
||||||
MessagingConstraints.CONTENT_TYPE(arrayOf(ContentType.SNAP))
|
MessagingConstraints.CONTENT_TYPE(arrayOf(ContentType.SNAP))
|
||||||
))
|
))
|
||||||
runCurrentTask()
|
runCurrentTask()
|
||||||
}
|
}
|
||||||
ActionButton(text = translation[if (hasSelection) "delete_selection_option" else "delete_all_option"], icon = Icons.Rounded.DeleteForever) {
|
ActionButton(text = translation[if (hasSelection) "delete_selection_option" else "delete_all_option"], icon = Icons.Rounded.DeleteForever) {
|
||||||
launchMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(messagingBridge.myUserId))) { message ->
|
launchMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(messagingBridge.myUserId), {
|
||||||
|
contentType != ContentType.STATUS.id
|
||||||
|
})) { message ->
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
message.contentType = ContentType.STATUS.id
|
message.contentType = ContentType.STATUS.id
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package me.rhunk.snapenhance.messaging
|
package me.rhunk.snapenhance.common.messaging
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableIntState
|
import androidx.compose.runtime.MutableIntState
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -67,9 +67,9 @@ class MessagingTask(
|
|||||||
error?.takeIf { error != "DUPLICATE_REQUEST" }?.let {
|
error?.takeIf { error != "DUPLICATE_REQUEST" }?.let {
|
||||||
onFailure(message, error)
|
onFailure(message, error)
|
||||||
}
|
}
|
||||||
onSuccess(message)
|
|
||||||
processedMessageCount.intValue++
|
processedMessageCount.intValue++
|
||||||
delay(Random.nextLong(20, 50))
|
onSuccess(message)
|
||||||
|
delay(Random.nextLong(50, 80))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,11 @@ package me.rhunk.snapenhance.core.action.impl
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@ -27,12 +31,15 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
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 kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import me.rhunk.snapenhance.common.data.ContentType
|
||||||
import me.rhunk.snapenhance.common.data.FriendLinkType
|
import me.rhunk.snapenhance.common.data.FriendLinkType
|
||||||
import me.rhunk.snapenhance.common.database.impl.FriendInfo
|
import me.rhunk.snapenhance.common.database.impl.FriendInfo
|
||||||
|
import me.rhunk.snapenhance.common.messaging.MessagingConstraints
|
||||||
|
import me.rhunk.snapenhance.common.messaging.MessagingTask
|
||||||
|
import me.rhunk.snapenhance.common.messaging.MessagingTaskType
|
||||||
import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
|
import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
|
||||||
import me.rhunk.snapenhance.common.util.ktx.copyToClipboard
|
import me.rhunk.snapenhance.common.util.ktx.copyToClipboard
|
||||||
import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie
|
import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie
|
||||||
@ -72,34 +79,45 @@ class BulkMessagingAction : AbstractAction() {
|
|||||||
ctx: Context,
|
ctx: Context,
|
||||||
ids: List<String>,
|
ids: List<String>,
|
||||||
delay: Pair<Long, Long>,
|
delay: Pair<Long, Long>,
|
||||||
action: (String) -> Unit = {},
|
action: suspend (id: String, setDialogMessage: (String) -> Unit) -> Unit = { _, _ -> }
|
||||||
): Job {
|
) = context.coroutineScope.launch {
|
||||||
var index = 0
|
val statusTextView = TextView(ctx)
|
||||||
val dialog = ViewAppearanceHelper.newAlertDialogBuilder(ctx)
|
val dialog = withContext(Dispatchers.Main) {
|
||||||
.setTitle("...")
|
ViewAppearanceHelper.newAlertDialogBuilder(ctx)
|
||||||
.setView(ProgressBar(ctx))
|
.setTitle("...")
|
||||||
.setCancelable(false)
|
.setView(LinearLayout(ctx).apply {
|
||||||
.show()
|
orientation = LinearLayout.VERTICAL
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
addView(statusTextView.apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||||
|
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||||
|
})
|
||||||
|
addView(ProgressBar(ctx))
|
||||||
|
})
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
return context.coroutineScope.launch {
|
ids.forEachIndexed { index, id ->
|
||||||
ids.forEach { id ->
|
launch(Dispatchers.Main) {
|
||||||
runCatching {
|
dialog.setTitle(
|
||||||
action(id)
|
translation.format("progress_status", "index" to (index + 1).toString(), "total" to ids.size.toString())
|
||||||
}.onFailure {
|
)
|
||||||
context.log.error("Failed to process $it", it)
|
|
||||||
context.shortToast("Failed to process $id")
|
|
||||||
}
|
|
||||||
index++
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
dialog.setTitle(
|
|
||||||
translation.format("progress_status", "index" to index.toString(), "total" to ids.size.toString())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
delay(Random.nextLong(delay.first, delay.second))
|
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
runCatching {
|
||||||
dialog.dismiss()
|
action(id) {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
statusTextView.text = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to process $it", it)
|
||||||
|
context.shortToast("Failed to process $id")
|
||||||
}
|
}
|
||||||
|
delay(Random.nextLong(delay.first, delay.second))
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,20 +396,22 @@ class BulkMessagingAction : AbstractAction() {
|
|||||||
Text(text = (friendInfo.displayName ?: friendInfo.mutableUsername).toString(), fontSize = 16.sp, fontWeight = FontWeight.Bold, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
Text(text = (friendInfo.displayName ?: friendInfo.mutableUsername).toString(), fontSize = 16.sp, fontWeight = FontWeight.Bold, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
||||||
Text(text = friendInfo.mutableUsername.toString(), fontSize = 10.sp, fontWeight = FontWeight.Light, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
Text(text = friendInfo.mutableUsername.toString(), fontSize = 10.sp, fontWeight = FontWeight.Light, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
||||||
}
|
}
|
||||||
Text(text = "Relationship: " + remember(friendInfo) {
|
val userInfo = remember(friendInfo) {
|
||||||
context.translation["friendship_link_type.${FriendLinkType.fromValue(friendInfo.friendLinkType).shortName}"]
|
buildString {
|
||||||
}, fontSize = 12.sp, fontWeight = FontWeight.Light)
|
append("Relationship: ")
|
||||||
remember(friendInfo) { friendInfo.addedTimestamp.takeIf { it > 0L }?.let {
|
append(context.translation["friendship_link_type.${FriendLinkType.fromValue(friendInfo.friendLinkType).shortName}"])
|
||||||
DateFormat.getDateTimeInstance().format(Date(friendInfo.addedTimestamp))
|
friendInfo.addedTimestamp.takeIf { it > 0L }?.let {
|
||||||
} }?.let {
|
append("\nAdded ${DateFormat.getDateTimeInstance().format(Date(it))}")
|
||||||
Text(text = "Added $it", fontSize = 12.sp, fontWeight = FontWeight.Light)
|
}
|
||||||
}
|
friendInfo.snapScore.takeIf { it > 0 }?.let {
|
||||||
remember(friendInfo) { friendInfo.snapScore.takeIf { it > 0 } }?.let {
|
append("\nSnap Score: $it")
|
||||||
Text(text = "Snap Score: $it", fontSize = 12.sp, fontWeight = FontWeight.Light)
|
}
|
||||||
}
|
friendInfo.streakLength.takeIf { it > 0 }?.let {
|
||||||
remember(friendInfo) { friendInfo.streakLength.takeIf { it > 0 } }?.let {
|
append("\nStreaks length: $it")
|
||||||
Text(text = "Streaks length: $it", fontSize = 12.sp, fontWeight = FontWeight.Light)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Text(text = userInfo, fontSize = 12.sp, fontWeight = FontWeight.Light, lineHeight = 16.sp, overflow = TextOverflow.Ellipsis)
|
||||||
}
|
}
|
||||||
|
|
||||||
Checkbox(
|
Checkbox(
|
||||||
@ -421,56 +441,65 @@ class BulkMessagingAction : AbstractAction() {
|
|||||||
|
|
||||||
val ctx = LocalContext.current
|
val ctx = LocalContext.current
|
||||||
|
|
||||||
|
val actions = remember {
|
||||||
|
mapOf<() -> String, () -> Unit>(
|
||||||
|
{ "Clean " + selectedFriends.size + " conversations" } to {
|
||||||
|
context.feature(Messaging::class).conversationManager?.getOneOnOneConversationIds(selectedFriends.toList().also {
|
||||||
|
selectedFriends.clear()
|
||||||
|
}, onError = { error ->
|
||||||
|
context.shortToast("Failed to fetch conversations: $error")
|
||||||
|
}, onSuccess = { conversations ->
|
||||||
|
removeAction(ctx, conversations.map { it.second }.distinct(), delay = 10L to 40L) { conversationId, setDialogMessage ->
|
||||||
|
cleanConversation(
|
||||||
|
conversationId, setDialogMessage
|
||||||
|
)
|
||||||
|
}.invokeOnCompletion {
|
||||||
|
coroutineScope.launch { refreshList() }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ "Remove " + selectedFriends.size + " friends" } to {
|
||||||
|
removeAction(ctx, selectedFriends.toList().also {
|
||||||
|
selectedFriends.clear()
|
||||||
|
}, delay = 500L to 1200L) { userId, _ -> removeFriend(userId) }.invokeOnCompletion {
|
||||||
|
coroutineScope.launch { refreshList() }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "Clean " + selectedFriends.size + " conversations and remove " + selectedFriends.size + " friends" } to {
|
||||||
|
context.feature(Messaging::class).conversationManager?.getOneOnOneConversationIds(selectedFriends.toList().also {
|
||||||
|
selectedFriends.clear()
|
||||||
|
}, onError = { error ->
|
||||||
|
context.shortToast("Failed to fetch conversations: $error")
|
||||||
|
}, onSuccess = { conversations ->
|
||||||
|
removeAction(ctx, conversations.map { it.second }.distinct(), delay = 500L to 1200L) { conversationId, setDialogMessage ->
|
||||||
|
cleanConversation(
|
||||||
|
conversationId, setDialogMessage
|
||||||
|
)
|
||||||
|
removeFriend(conversations.firstOrNull { it.second == conversationId }?.first ?: return@removeAction)
|
||||||
|
}.invokeOnCompletion {
|
||||||
|
coroutineScope.launch { refreshList() }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Button(
|
actions.forEach { (text, actionFunction) ->
|
||||||
modifier = Modifier
|
Button(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.padding(2.dp),
|
.fillMaxWidth()
|
||||||
onClick = {
|
.padding(2.dp),
|
||||||
showConfirmationDialog = true
|
onClick = {
|
||||||
action = {
|
showConfirmationDialog = true
|
||||||
val messaging = context.feature(Messaging::class)
|
action = actionFunction
|
||||||
messaging.conversationManager?.apply {
|
},
|
||||||
getOneOnOneConversationIds(selectedFriends, onError = { error ->
|
enabled = selectedFriends.isNotEmpty()
|
||||||
context.shortToast("Failed to fetch conversations: $error")
|
) {
|
||||||
}, onSuccess = { conversations ->
|
Text(text = remember(selectedFriends.size) { text() })
|
||||||
context.runOnUiThread {
|
}
|
||||||
removeAction(ctx, conversations.map { it.second }.distinct(), delay = 100L to 400L) {
|
|
||||||
messaging.clearConversationFromFeed(it, onError = { error ->
|
|
||||||
context.shortToast("Failed to clear conversation: $error")
|
|
||||||
})
|
|
||||||
}.invokeOnCompletion {
|
|
||||||
coroutineScope.launch { refreshList() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
selectedFriends.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = selectedFriends.isNotEmpty()
|
|
||||||
) {
|
|
||||||
Text(text = "Clear " + selectedFriends.size + " conversations")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(2.dp),
|
|
||||||
onClick = {
|
|
||||||
showConfirmationDialog = true
|
|
||||||
action = {
|
|
||||||
removeAction(ctx, selectedFriends.toList().also {
|
|
||||||
selectedFriends.clear()
|
|
||||||
}, delay = 500L to 1200L) { removeFriend(it) }.invokeOnCompletion {
|
|
||||||
coroutineScope.launch { refreshList() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = selectedFriends.isNotEmpty()
|
|
||||||
) {
|
|
||||||
Text(text = "Remove " + selectedFriends.size + " friends")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -520,4 +549,23 @@ class BulkMessagingAction : AbstractAction() {
|
|||||||
}.invoke(completable)
|
}.invoke(completable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun cleanConversation(
|
||||||
|
conversationId: String,
|
||||||
|
setDialogMessage: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val messageCount = mutableIntStateOf(0)
|
||||||
|
MessagingTask(
|
||||||
|
context.messagingBridge,
|
||||||
|
conversationId,
|
||||||
|
taskType = MessagingTaskType.DELETE,
|
||||||
|
constraints = listOf(MessagingConstraints.MY_USER_ID(context.messagingBridge), {
|
||||||
|
contentType != ContentType.STATUS.id
|
||||||
|
}),
|
||||||
|
processedMessageCount = messageCount,
|
||||||
|
onSuccess = {
|
||||||
|
setDialogMessage("${messageCount.intValue} deleted messages")
|
||||||
|
},
|
||||||
|
).run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user