commit 4b49d669b7ec8ee0599f621b64b3e8ad674a5dfe
parent 9f098834cfa61d80bc3c5fcac98e416c6f4b2be3
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Sun, 15 Oct 2023 17:47:19 +0200
feat(social/messaging_tasks): constraint dialog
Diffstat:
2 files changed, 152 insertions(+), 41 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/messaging/MessagingTask.kt b/app/src/main/kotlin/me/rhunk/snapenhance/messaging/MessagingTask.kt
@@ -36,9 +36,10 @@ object MessagingConstraints {
this.senderId == myUserId
}
}
- val CONTENT_TYPE: (ContentType) -> MessagingTaskConstraint = { contentType: ContentType ->
+ val CONTENT_TYPE: (Array<ContentType>) -> MessagingTaskConstraint = {
+ val contentTypes = it.map { type -> type.id };
{
- this.contentType == contentType.id
+ contentTypes.contains(this.contentType)
}
}
}
@@ -46,10 +47,10 @@ object MessagingConstraints {
class MessagingTask(
private val messagingBridge: MessagingBridge,
private val conversationId: String,
- private val taskType: MessagingTaskType,
- private val constraints: List<MessagingTaskConstraint>,
+ val taskType: MessagingTaskType,
+ val constraints: List<MessagingTaskConstraint>,
private val processedMessageCount: MutableIntState,
- private val onSuccess: (message: Message) -> Unit = {},
+ val onSuccess: (message: Message) -> Unit = {},
private val onFailure: (message: Message, reason: String) -> Unit = { _, _ -> },
private val overrideClientMessageIds: List<Long>? = null,
private val amountToProcess: Int? = null,
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/social/MessagingPreview.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/social/MessagingPreview.kt
@@ -82,27 +82,100 @@ class MessagingPreview(
}
@Composable
+ private fun ConstraintsSelectionDialog(
+ onChoose: (Array<ContentType>) -> Unit,
+ onDismiss: () -> Unit
+ ) {
+ val selectedTypes = remember { mutableStateListOf<ContentType>() }
+ var selectAllState by remember { mutableStateOf(false) }
+ val availableTypes = remember { arrayOf(
+ ContentType.CHAT,
+ ContentType.NOTE,
+ ContentType.SNAP,
+ ContentType.STICKER,
+ ContentType.EXTERNAL_MEDIA
+ ) }
+
+ fun toggleContentType(contentType: ContentType) {
+ if (selectAllState) return
+ if (selectedTypes.contains(contentType)) {
+ selectedTypes.remove(contentType)
+ } else {
+ selectedTypes.add(contentType)
+ }
+ }
+
+ Surface(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.surface)
+ ) {
+ Column(
+ modifier = Modifier.padding(15.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(5.dp)
+ ) {
+ Text("Choose content types to process")
+ Spacer(modifier = Modifier.height(5.dp))
+ availableTypes.forEach { contentType ->
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(2.dp)
+ .pointerInput(Unit) {
+ detectTapGestures(onTap = { toggleContentType(contentType) })
+ },
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Checkbox(
+ checked = selectedTypes.contains(contentType),
+ enabled = !selectAllState,
+ onCheckedChange = { toggleContentType(contentType) }
+ )
+ Text(text = contentType.toString())
+ }
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(5.dp),
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Switch(checked = selectAllState, onCheckedChange = {
+ selectAllState = it
+ })
+ Text(text = "Select all")
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ ) {
+ Button(onClick = { onDismiss() }) {
+ Text("Cancel")
+ }
+ Button(onClick = {
+ onChoose(if (selectAllState) ContentType.entries.toTypedArray()
+ else selectedTypes.toTypedArray())
+ }) {
+ Text("Continue")
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
fun TopBarAction() {
- var showDropDown by remember { mutableStateOf(false) }
+ var taskSelectionDropdown by remember { mutableStateOf(false) }
+ var selectConstraintsDialog by remember { mutableStateOf(false) }
var activeTask by remember { mutableStateOf(null as MessagingTask?) }
var activeJob by remember { mutableStateOf(null as Job?) }
val processMessageCount = remember { mutableIntStateOf(0) }
- fun triggerMessagingTask(taskType: MessagingTaskType, constraints: List<MessagingTaskConstraint> = listOf(), onSuccess: (Message) -> Unit = {}) {
- showDropDown = false
- processMessageCount.intValue = 0
- activeTask = MessagingTask(
- messagingBridge = messagingBridge,
- conversationId = conversationId!!,
- taskType = taskType,
- constraints = constraints,
- overrideClientMessageIds = selectedMessages.takeIf { it.isNotEmpty() }?.toList(),
- processedMessageCount = processMessageCount,
- onFailure = { message, reason ->
- context.log.verbose("Failed to process message ${message.clientMessageId}: $reason")
- }
- )
- selectedMessages.clear()
+ fun runCurrentTask() {
activeJob = coroutineScope.launch(Dispatchers.IO) {
activeTask?.run()
withContext(Dispatchers.Main) {
@@ -120,21 +193,58 @@ class MessagingPreview(
}
}
+ fun launchMessagingTask(taskType: MessagingTaskType, constraints: List<MessagingTaskConstraint> = listOf(), onSuccess: (Message) -> Unit = {}) {
+ taskSelectionDropdown = false
+ processMessageCount.intValue = 0
+ activeTask = MessagingTask(
+ messagingBridge, conversationId!!, taskType, constraints,
+ overrideClientMessageIds = selectedMessages.takeIf { it.isNotEmpty() }?.toList(),
+ processedMessageCount = processMessageCount,
+ onSuccess = onSuccess,
+ onFailure = { message, reason ->
+ context.log.verbose("Failed to process message ${message.clientMessageId}: $reason")
+ }
+ )
+ selectedMessages.clear()
+ }
+
+ if (selectConstraintsDialog && activeTask != null) {
+ Dialog(onDismissRequest = {
+ selectConstraintsDialog = false
+ activeTask = null
+ }) {
+ ConstraintsSelectionDialog(
+ onChoose = { contentTypes ->
+ launchMessagingTask(
+ taskType = activeTask!!.taskType,
+ constraints = activeTask!!.constraints + MessagingConstraints.CONTENT_TYPE(contentTypes),
+ onSuccess = activeTask!!.onSuccess
+ )
+ runCurrentTask()
+ selectConstraintsDialog = false
+ },
+ onDismiss = {
+ selectConstraintsDialog = false
+ activeTask = null
+ }
+ )
+ }
+ }
+
if (activeJob != null) {
Dialog(onDismissRequest = {
activeJob?.cancel()
activeJob = null
activeTask = null
}) {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .background(MaterialTheme.colorScheme.surface)
- .border(1.dp, MaterialTheme.colorScheme.onSurface, RoundedCornerShape(20.dp))
- .padding(15.dp),
+ Column(modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.surface)
+ .padding(15.dp)
+ .border(1.dp, MaterialTheme.colorScheme.onSurface, RoundedCornerShape(20.dp)),
horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(5.dp)
- ) {
+ verticalArrangement = Arrangement.spacedBy(5.dp))
+ {
Text("Processed ${processMessageCount.intValue} messages")
if (activeTask?.hasFixedGoal() == true) {
LinearProgressIndicator(
@@ -157,14 +267,12 @@ class MessagingPreview(
}
}
- IconButton(onClick = { showDropDown = !showDropDown }) {
+ IconButton(onClick = { taskSelectionDropdown = !taskSelectionDropdown }) {
Icon(imageVector = Icons.Filled.MoreVert, contentDescription = null)
}
if (selectedMessages.isNotEmpty()) {
- IconButton(onClick = {
- selectedMessages.clear()
- }) {
+ IconButton(onClick = { selectedMessages.clear() }) {
Icon(imageVector = Icons.Filled.Close, contentDescription = "Close")
}
}
@@ -177,30 +285,32 @@ class MessagingPreview(
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(50.dp))
) {
DropdownMenu(
- expanded = showDropDown, onDismissRequest = {
- showDropDown = false
- }
+ expanded = taskSelectionDropdown, onDismissRequest = { taskSelectionDropdown = false }
) {
val hasSelection = selectedMessages.isNotEmpty()
ActionButton(text = if (hasSelection) "Save selection" else "Save all", icon = Icons.Rounded.BookmarkAdded) {
- triggerMessagingTask(MessagingTaskType.SAVE)
+ launchMessagingTask(MessagingTaskType.SAVE)
+ selectConstraintsDialog = true
}
ActionButton(text = if (hasSelection) "Unsave selection" else "Unsave all", icon = Icons.Rounded.BookmarkBorder) {
- triggerMessagingTask(MessagingTaskType.UNSAVE)
+ launchMessagingTask(MessagingTaskType.UNSAVE)
+ selectConstraintsDialog = true
}
ActionButton(text = if (hasSelection) "Mark selected Snap as seen" else "Mark all Snaps as seen", icon = Icons.Rounded.RemoveRedEye) {
- triggerMessagingTask(MessagingTaskType.READ, listOf(
+ launchMessagingTask(MessagingTaskType.READ, listOf(
MessagingConstraints.NO_USER_ID(myUserId),
- MessagingConstraints.CONTENT_TYPE(ContentType.SNAP)
+ MessagingConstraints.CONTENT_TYPE(arrayOf(ContentType.SNAP))
))
+ runCurrentTask()
}
ActionButton(text = if (hasSelection) "Delete selected" else "Delete all", icon = Icons.Rounded.DeleteForever) {
- triggerMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(myUserId))) { message ->
+ launchMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(myUserId))) { message ->
coroutineScope.launch {
messages.remove(message.serverMessageId)
messageSize = messages.size
}
}
+ selectConstraintsDialog = true
}
}
}