Update instead of cancel notification on replies (#3167)

* Update instead of cancel notification on reply

 - When a notification is replied to, update it instead of cancelling it to allow the other to continue manipulating it

* Support multiple replies

 - Keep a history of replies in the bundle to match system behavior

* Only keep replies that are actually shown
This commit is contained in:
Joris Pelgröm 2022-12-31 02:15:36 +01:00 committed by GitHub
parent 37e00233bd
commit 4ff2fcca4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 31 deletions

View file

@ -43,6 +43,8 @@ import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.text.HtmlCompat
import androidx.core.text.isDigitsOnly
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.toAndroidIconCompat
import com.vdurmont.emoji.EmojiParser
@ -286,27 +288,44 @@ class MessagingManager @Inject constructor(
const val VIDEO_START_MICROSECONDS = 100000L
const val VIDEO_INCREMENT_MICROSECONDS = 750000L
const val VIDEO_GUESS_MILLISECONDS = 7000L
// Values for a notification that has been replied to
const val SOURCE_REPLY = "REPLY_"
const val SOURCE_REPLY_HISTORY = "reply_history_"
}
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
private var textToSpeech: TextToSpeech? = null
fun handleMessage(jsonData: Map<String, String>, source: String) {
fun handleMessage(notificationData: Map<String, String>, source: String) {
val jsonObject = JSONObject(jsonData)
val now = System.currentTimeMillis()
val notificationRow =
NotificationItem(0, now, jsonData[MESSAGE].toString(), jsonObject.toString(), source)
notificationDao.add(notificationRow)
var now = System.currentTimeMillis()
var jsonData = notificationData
val notificationId: Long
val confirmation = jsonData[CONFIRMATION]?.toBoolean() ?: false
if (confirmation) {
mainScope.launch {
try {
integrationUseCase.fireEvent("mobile_app_notification_received", jsonData)
} catch (e: Exception) {
Log.e(TAG, "Unable to send notification received event", e)
if (source.startsWith(SOURCE_REPLY)) {
notificationId = source.substringAfter(SOURCE_REPLY).toLong()
notificationDao.get(notificationId.toInt())?.let {
val dbData: Map<String, String> = jacksonObjectMapper().readValue(it.data)
now = it.received // Allow for updating the existing notification without a tag
jsonData = jsonData + dbData // Add the notificationData, this contains the reply text
} ?: return
} else {
val jsonObject = JSONObject(jsonData)
val notificationRow =
NotificationItem(0, now, jsonData[MESSAGE].toString(), jsonObject.toString(), source)
notificationId = notificationDao.add(notificationRow)
val confirmation = jsonData[CONFIRMATION]?.toBoolean() ?: false
if (confirmation) {
mainScope.launch {
try {
integrationUseCase.fireEvent("mobile_app_notification_received", jsonData)
} catch (e: Exception) {
Log.e(TAG, "Unable to send notification received event", e)
}
}
}
}
@ -598,7 +617,7 @@ class MessagingManager @Inject constructor(
}
else -> mainScope.launch {
Log.d(TAG, "Creating notification with following data: $jsonData")
sendNotification(jsonData)
sendNotification(jsonData, notificationId, now)
}
}
}
@ -1033,11 +1052,11 @@ class MessagingManager @Inject constructor(
* Create and show a simple notification containing the received FCM message.
*
*/
private suspend fun sendNotification(data: Map<String, String>) {
private suspend fun sendNotification(data: Map<String, String>, id: Long? = null, received: Long? = null) {
val notificationManagerCompat = NotificationManagerCompat.from(context)
val tag = data["tag"]
val messageId = tag?.hashCode() ?: System.currentTimeMillis().toInt()
val messageId = tag?.hashCode() ?: received?.toInt() ?: System.currentTimeMillis().toInt()
var group = data["group"]
var groupId = 0
@ -1087,7 +1106,9 @@ class MessagingManager @Inject constructor(
handleVisibility(notificationBuilder, data)
handleActions(notificationBuilder, tag, messageId, data)
handleActions(notificationBuilder, tag, messageId, id, data)
handleReplyHistory(notificationBuilder, data)
handleDeleteIntent(notificationBuilder, data, messageId, group, groupId)
@ -1599,6 +1620,7 @@ class MessagingManager @Inject constructor(
builder: NotificationCompat.Builder,
tag: String?,
messageId: Int,
databaseId: Long?,
data: Map<String, String>
) {
for (i in 1..3) {
@ -1619,6 +1641,10 @@ class MessagingManager @Inject constructor(
NotificationActionReceiver.EXTRA_NOTIFICATION_ACTION,
notificationAction
)
putExtra(
NotificationActionReceiver.EXTRA_NOTIFICATION_DB,
databaseId
)
}
when (notificationAction.key) {
@ -1638,7 +1664,7 @@ class MessagingManager @Inject constructor(
}
val replyPendingIntent = PendingIntent.getBroadcast(
context,
0,
messageId,
eventIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
@ -1735,6 +1761,22 @@ class MessagingManager @Inject constructor(
)
}
private fun handleReplyHistory(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val replies = data.entries
.filter { it.key.startsWith(SOURCE_REPLY_HISTORY) }
.sortedBy { it.key.substringAfter(SOURCE_REPLY_HISTORY).toInt() }
if (replies.any()) {
val history = replies.map { it.value }.reversed().toTypedArray() // Reverse to have latest replies first
builder.setRemoteInputHistory(history)
builder.setOnlyAlertOnce(true) // Overwrites user settings to match system defaults
}
}
}
private fun handleChannel(
notificationManagerCompat: NotificationManagerCompat,
data: Map<String, String>

View file

@ -27,6 +27,7 @@ class NotificationActionReceiver : BroadcastReceiver() {
const val FIRE_EVENT = "FIRE_EVENT"
const val EXTRA_NOTIFICATION_TAG = "EXTRA_NOTIFICATION_TAG"
const val EXTRA_NOTIFICATION_ID = "EXTRA_NOTIFICATION_ID"
const val EXTRA_NOTIFICATION_DB = "EXTRA_NOTIFICATION_DB"
const val EXTRA_NOTIFICATION_ACTION = "EXTRA_ACTION_KEY"
}
@ -35,6 +36,9 @@ class NotificationActionReceiver : BroadcastReceiver() {
@Inject
lateinit var integrationUseCase: IntegrationRepository
@Inject
lateinit var messagingManager: MessagingManager
override fun onReceive(context: Context, intent: Intent) {
val notificationAction =
@ -47,24 +51,44 @@ class NotificationActionReceiver : BroadcastReceiver() {
val tag = intent.getStringExtra(EXTRA_NOTIFICATION_TAG)
val messageId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
val databaseId = intent.getLongExtra(EXTRA_NOTIFICATION_DB, 0)
val isReply = notificationAction.key == "REPLY"
var replyText: String? = null
val onComplete: () -> Unit = {
val notificationManagerCompat = NotificationManagerCompat.from(context)
notificationManagerCompat.cancel(
tag,
messageId,
true
)
if (isReply && !replyText.isNullOrBlank()) {
val replies = notificationAction.data.entries
.filter { it.key.startsWith(MessagingManager.SOURCE_REPLY_HISTORY) }
.sortedBy { it.key.substringAfter(MessagingManager.SOURCE_REPLY_HISTORY).toInt() }
.map { it.value } + replyText
messagingManager.handleMessage(
replies
.takeLast(3)
.mapIndexed { index, text ->
"${MessagingManager.SOURCE_REPLY_HISTORY}$index" to text!!
}
.toMap(),
"${MessagingManager.SOURCE_REPLY}$databaseId"
)
} else {
val notificationManagerCompat = NotificationManagerCompat.from(context)
notificationManagerCompat.cancel(
tag,
messageId,
true
)
}
}
val onFailure: () -> Unit = {
Handler(context.mainLooper).post {
Toast.makeText(context, commonR.string.event_error, Toast.LENGTH_LONG).show()
}
}
if (notificationAction.key == "REPLY") {
notificationAction.data += Pair(
"reply_text",
RemoteInput.getResultsFromIntent(intent)?.getCharSequence(KEY_TEXT_REPLY).toString()
)
if (isReply) {
replyText = RemoteInput.getResultsFromIntent(intent)?.getCharSequence(KEY_TEXT_REPLY).toString()
notificationAction.data += Pair("reply_text", replyText)
}
when (intent.action) {
@ -81,7 +105,9 @@ class NotificationActionReceiver : BroadcastReceiver() {
try {
integrationUseCase.fireEvent(
"mobile_app_notification_action",
action.data.plus(Pair("action", action.key))
action.data
.filter { !it.key.startsWith(MessagingManager.SOURCE_REPLY_HISTORY) }
.plus(Pair("action", action.key))
)
onComplete()
} catch (e: Exception) {

View file

@ -9,7 +9,10 @@ import androidx.room.Query
interface NotificationDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(notification: NotificationItem)
fun add(notification: NotificationItem): Long
@Query("SELECT * FROM notification_history WHERE id = :id")
fun get(id: Int): NotificationItem?
@Query("SELECT * FROM notification_history ORDER BY received DESC")
fun getAll(): Array<NotificationItem>?