Merge pull request #3916 from element-hq/feature/bma/notificationActions

Enable all notification actions: quick reply, accept/decline invite, mark as read from notification.
This commit is contained in:
Benoit Marty
2024-11-26 18:01:37 +01:00
committed by GitHub
8 changed files with 58 additions and 22 deletions

View File

@@ -11,14 +11,20 @@ import android.graphics.Color
import androidx.annotation.ColorInt
object NotificationConfig {
// TODO EAx Implement and set to true at some point
const val SUPPORT_MARK_AS_READ_ACTION = false
/**
* If set to true, the notification will have a "Mark as read" action.
*/
const val SHOW_MARK_AS_READ_ACTION = true
// TODO EAx Implement and set to true at some point
const val SUPPORT_JOIN_DECLINE_INVITE = false
/**
* If set to true, the notification for invitation will have two actions to accept or decline the invite.
*/
const val SHOW_ACCEPT_AND_DECLINE_INVITE_ACTIONS = true
// TODO EAx Implement and set to true at some point
const val SUPPORT_QUICK_REPLY_ACTION = false
/**
* If set to true, the notification will have a "Quick reply" action, allow to compose and send a message to the room.
*/
const val SHOW_QUICK_REPLY_ACTION = true
@ColorInt
val NOTIFICATION_ACCENT_COLOR: Int = Color.parseColor("#FF0DBD8B")

View File

@@ -230,10 +230,8 @@ class DefaultNotificationCreator @Inject constructor(
.setSmallIcon(smallIcon)
.setColor(accentColor)
.apply {
if (NotificationConfig.SUPPORT_JOIN_DECLINE_INVITE) {
addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent))
addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent))
}
addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent))
addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent))
// Build the pending intent for when the notification is clicked
setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(inviteNotifiableEvent.sessionId, inviteNotifiableEvent.roomId))

View File

@@ -11,12 +11,14 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import io.element.android.appconfig.NotificationConfig
import io.element.android.libraries.androidutils.uri.createIgnoredUri
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import io.element.android.services.toolbox.api.systemclock.SystemClock
import javax.inject.Inject
@@ -27,8 +29,8 @@ class AcceptInvitationActionFactory @Inject constructor(
private val stringProvider: StringProvider,
private val clock: SystemClock,
) {
// offer to type a quick accept button
fun create(inviteNotifiableEvent: InviteNotifiableEvent): NotificationCompat.Action {
fun create(inviteNotifiableEvent: InviteNotifiableEvent): NotificationCompat.Action? {
if (!NotificationConfig.SHOW_ACCEPT_AND_DECLINE_INVITE_ACTIONS) return null
val sessionId = inviteNotifiableEvent.sessionId.value
val roomId = inviteNotifiableEvent.roomId.value
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
@@ -44,7 +46,7 @@ class AcceptInvitationActionFactory @Inject constructor(
)
return NotificationCompat.Action.Builder(
R.drawable.vector_notification_accept_invitation,
stringProvider.getString(R.string.notification_invitation_action_join),
stringProvider.getString(CommonStrings.action_accept),
pendingIntent
).build()
}

View File

@@ -29,7 +29,7 @@ class MarkAsReadActionFactory @Inject constructor(
private val clock: SystemClock,
) {
fun create(roomInfo: RoomEventGroupInfo): NotificationCompat.Action? {
if (!NotificationConfig.SUPPORT_MARK_AS_READ_ACTION) return null
if (!NotificationConfig.SHOW_MARK_AS_READ_ACTION) return null
val sessionId = roomInfo.sessionId.value
val roomId = roomInfo.roomId.value
val intent = Intent(context, NotificationBroadcastReceiver::class.java)

View File

@@ -34,7 +34,7 @@ class QuickReplyActionFactory @Inject constructor(
private val clock: SystemClock,
) {
fun create(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): NotificationCompat.Action? {
if (!NotificationConfig.SUPPORT_QUICK_REPLY_ACTION) return null
if (!NotificationConfig.SHOW_QUICK_REPLY_ACTION) return null
val sessionId = roomInfo.sessionId
val roomId = roomInfo.roomId
val replyPendingIntent = buildQuickReplyIntent(sessionId, roomId, threadId)

View File

@@ -11,12 +11,14 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import io.element.android.appconfig.NotificationConfig
import io.element.android.libraries.androidutils.uri.createIgnoredUri
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import io.element.android.services.toolbox.api.systemclock.SystemClock
import javax.inject.Inject
@@ -28,6 +30,7 @@ class RejectInvitationActionFactory @Inject constructor(
private val clock: SystemClock,
) {
fun create(inviteNotifiableEvent: InviteNotifiableEvent): NotificationCompat.Action? {
if (!NotificationConfig.SHOW_ACCEPT_AND_DECLINE_INVITE_ACTIONS) return null
val sessionId = inviteNotifiableEvent.sessionId.value
val roomId = inviteNotifiableEvent.roomId.value
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
@@ -41,10 +44,9 @@ class RejectInvitationActionFactory @Inject constructor(
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Action.Builder(
R.drawable.vector_notification_reject_invitation,
stringProvider.getString(R.string.notification_invitation_action_reject),
stringProvider.getString(CommonStrings.action_reject),
pendingIntent
).build()
}

View File

@@ -11,12 +11,15 @@ import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import com.google.common.truth.Truth.assertThat
import io.element.android.appconfig.NotificationConfig
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_TIMESTAMP
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL
import io.element.android.libraries.matrix.ui.media.MediaRequestData
import io.element.android.libraries.push.impl.notifications.factories.MARK_AS_READ_ACTION_TITLE
import io.element.android.libraries.push.impl.notifications.factories.QUICK_REPLY_ACTION_TITLE
import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.test.notifications.FakeImageLoader
@@ -156,6 +159,13 @@ class DefaultRoomGroupMessageCreatorTest {
)
assertThat(result.number).isEqualTo(2)
assertThat(result.`when`).isEqualTo(A_TIMESTAMP + 10)
val actionTitles = result.actions?.map { it.title }
assertThat(actionTitles).isEqualTo(
listOfNotNull(
MARK_AS_READ_ACTION_TITLE.takeIf { NotificationConfig.SHOW_MARK_AS_READ_ACTION },
QUICK_REPLY_ACTION_TITLE.takeIf { NotificationConfig.SHOW_QUICK_REPLY_ACTION },
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}
@@ -175,7 +185,12 @@ class DefaultRoomGroupMessageCreatorTest {
imageLoader = fakeImageLoader.getImageLoader(),
existingNotification = null,
)
assertThat(result.actions).isNull()
val actionTitles = result.actions?.map { it.title }
assertThat(actionTitles).isEqualTo(
listOfNotNull(
MARK_AS_READ_ACTION_TITLE.takeIf { NotificationConfig.SHOW_MARK_AS_READ_ACTION }
)
)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0)
}

View File

@@ -13,6 +13,7 @@ import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.common.truth.Truth.assertThat
import io.element.android.appconfig.NotificationConfig
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
@@ -151,6 +152,13 @@ class DefaultNotificationCreatorTest {
result.commonAssertions(
expectedCategory = null,
)
val actionTitles = result.actions?.map { it.title }
assertThat(actionTitles).isEqualTo(
listOfNotNull(
REJECT_INVITATION_ACTION_TITLE.takeIf { NotificationConfig.SHOW_ACCEPT_AND_DECLINE_INVITE_ACTIONS },
ACCEPT_INVITATION_ACTION_TITLE.takeIf { NotificationConfig.SHOW_ACCEPT_AND_DECLINE_INVITE_ACTIONS },
)
)
}
@Test
@@ -271,6 +279,11 @@ class DefaultNotificationCreatorTest {
}
}
const val MARK_AS_READ_ACTION_TITLE = "MarkAsReadAction"
const val QUICK_REPLY_ACTION_TITLE = "QuickReplyAction"
const val ACCEPT_INVITATION_ACTION_TITLE = "AcceptInvitationAction"
const val REJECT_INVITATION_ACTION_TITLE = "RejectInvitationAction"
fun createNotificationCreator(
context: Context = RuntimeEnvironment.getApplication(),
buildMeta: BuildMeta = aBuildMeta(),
@@ -291,26 +304,26 @@ fun createNotificationCreator(
markAsReadActionFactory = MarkAsReadActionFactory(
context = context,
actionIds = NotificationActionIds(buildMeta),
stringProvider = FakeStringProvider("MarkAsReadActionFactory"),
stringProvider = FakeStringProvider(MARK_AS_READ_ACTION_TITLE),
clock = FakeSystemClock(),
),
quickReplyActionFactory = QuickReplyActionFactory(
context = context,
actionIds = NotificationActionIds(buildMeta),
stringProvider = FakeStringProvider("QuickReplyActionFactory"),
stringProvider = FakeStringProvider(QUICK_REPLY_ACTION_TITLE),
clock = FakeSystemClock(),
),
bitmapLoader = bitmapLoader,
acceptInvitationActionFactory = AcceptInvitationActionFactory(
context = context,
actionIds = NotificationActionIds(buildMeta),
stringProvider = FakeStringProvider("AcceptInvitationActionFactory"),
stringProvider = FakeStringProvider(ACCEPT_INVITATION_ACTION_TITLE),
clock = FakeSystemClock(),
),
rejectInvitationActionFactory = RejectInvitationActionFactory(
context = context,
actionIds = NotificationActionIds(buildMeta),
stringProvider = FakeStringProvider("RejectInvitationActionFactory"),
stringProvider = FakeStringProvider(REJECT_INVITATION_ACTION_TITLE),
clock = FakeSystemClock(),
),
)