diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ea6add7d..0647d4d1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -66,6 +66,10 @@
+
+
user in payload, abort")
+ val body = data["body"] ?: run {
+ logcat(LogPriority.ERROR) { "No body in message, abort" }
return
}
- if (authorIcon == null) {
- authorIcon =
- "$STOAT_BASE/users/${message.author?.ifBlank { "0".repeat(26) }}/default_avatar"
+ val image = data["image"] ?: run {
+ logcat(LogPriority.WARN) { "No image in message, abort" }
+ return
}
+ val authorName = data["author_name"] ?: run {
+ logcat(LogPriority.ERROR) { "No author name in message, abort" }
+ return
+ }
+
+ val channelId = data["channel"] ?: run {
+ logcat(LogPriority.ERROR) { "No channel in message, abort" }
+ return
+ }
+
+ val messageId = data["message"] ?: run {
+ logcat(LogPriority.ERROR) { "No message ID in message, abort" }
+ return
+ }
+
+ val messageTimestamp = ULID.asTimestamp(messageId)
+
val db = Database(SqlStorage.driver)
- val channelName = message.channel?.let {
- db.channelQueries.findById(it).executeAsOneOrNull()
- }?.let {
+ val channelName = db.channelQueries.findById(channelId).executeAsOneOrNull()?.let {
when (it.channelType) {
- "DirectMessage" -> {
- user.displayName ?: user.username
- }
-
- "TextChannel" -> {
- "#${it.name}"
- }
-
- else -> {
- it.name ?: getString(R.string.unknown)
- }
+ "DirectMessage" -> authorName
+ "TextChannel" -> "#${it.name}"
+ else -> it.name ?: authorName
}
- } ?: getString(
- R.string.unknown
- )
-
- val messageTimestamp = message.id?.let { ULID.asTimestamp(it) } ?: run {
- Log.e("HandlerService", "No message id in message, abort")
- return
+ } ?: runBlocking {
+ runCatching { fetchSingleChannel(channelId) }.getOrNull()?.let {
+ when (it.channelType) {
+ ChannelType.DirectMessage -> authorName
+ ChannelType.TextChannel -> "#${it.name}"
+ else -> it.name ?: authorName
+ }
+ } ?: authorName
}
val bitmap = Glide.with(this)
.asBitmap()
- .load(authorIcon)
+ .load(image)
.circleCrop()
.submit()
.get()
- val author =
- Person.Builder()
- .setBot(user.bot != null)
- .setKey(message.author)
- .setIcon(IconCompat.createWithBitmap(bitmap))
- .setName(user.displayName ?: user.username)
- .build()
+ val author = Person.Builder()
+ .setBot(false)
+ .setKey(authorId)
+ .setIcon(IconCompat.createWithBitmap(bitmap))
+ .setName(authorName)
+ .build()
- if (message.channel == null) {
- Log.e("HandlerService", "No channel in message, abort")
- return
- }
-
- val shortcutId = "${BuildConfig.APPLICATION_ID}.channel.${message.channel}"
+ val shortcutId = "${BuildConfig.APPLICATION_ID}.channel.$channelId"
val conversationIntent = Intent(this, MainActivity::class.java).apply {
action = Intent.ACTION_VIEW
- putExtra("channelId", message.channel)
+ putExtra("channelId", channelId)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
@@ -156,14 +138,18 @@ class HandlerService : FirebaseMessagingService() {
build()
}
+ val replyIntent = Intent(this, ReplyReceiver::class.java).apply {
+ putExtra("channelId", channelId)
+ }
+
val action: NotificationCompat.Action =
NotificationCompat.Action.Builder(
R.drawable.ic_reply_24dp,
getString(R.string.message_context_sheet_actions_reply),
- PendingIntent.getActivity(
+ PendingIntent.getBroadcast(
this,
- 0,
- conversationIntent,
+ channelId.hashCode(),
+ replyIntent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
@@ -172,25 +158,21 @@ class HandlerService : FirebaseMessagingService() {
val contentIntent = PendingIntent.getActivity(
this,
- message.channel.hashCode(),
+ channelId.hashCode(),
conversationIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val builder = NotificationCompat.Builder(this, CHANNEL_ID_GROUP_CONVERSATIONS_MESSAGES)
- .setSmallIcon(R.drawable.ic_chat_24dp)
- .setContentTitle(user.displayName ?: user.username)
- .setContentText(message.content)
+ .setSmallIcon(R.drawable.ic_stoat_24dp)
+ .setContentTitle(authorName)
+ .setContentText(body)
.setContentIntent(contentIntent)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setStyle(
NotificationCompat.MessagingStyle(author)
.setConversationTitle(channelName)
- .addMessage(
- message.content ?: getString(R.string.reply_message_empty_has_attachments),
- messageTimestamp,
- author
- )
+ .addMessage(body, messageTimestamp, author)
)
.addAction(action)
.setPriority(NotificationCompat.PRIORITY_HIGH)
@@ -203,7 +185,7 @@ class HandlerService : FirebaseMessagingService() {
val bubbleIntent = PendingIntent.getActivity(
this,
- message.channel.hashCode(),
+ channelId.hashCode(),
conversationIntent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
@@ -228,8 +210,7 @@ class HandlerService : FirebaseMessagingService() {
) {
return
}
- notify(message.channel, NotificationID.NEW_MESSAGE, builder.build())
+ notify(channelId, NotificationID.NEW_MESSAGE, builder.build())
}
- /// END TEMPORARY CODE
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/chat/stoat/c2dm/NotificationDeepLink.kt b/app/src/main/java/chat/stoat/c2dm/NotificationDeepLink.kt
new file mode 100644
index 00000000..43ba9ec5
--- /dev/null
+++ b/app/src/main/java/chat/stoat/c2dm/NotificationDeepLink.kt
@@ -0,0 +1,7 @@
+package chat.stoat.c2dm
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+object NotificationDeepLink {
+ val pendingChannelId = MutableStateFlow(null)
+}
diff --git a/app/src/main/java/chat/stoat/c2dm/ReplyReceiver.kt b/app/src/main/java/chat/stoat/c2dm/ReplyReceiver.kt
new file mode 100644
index 00000000..448f57ca
--- /dev/null
+++ b/app/src/main/java/chat/stoat/c2dm/ReplyReceiver.kt
@@ -0,0 +1,44 @@
+package chat.stoat.c2dm
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.app.RemoteInput
+import chat.stoat.api.StoatAPI
+import chat.stoat.api.routes.channel.sendMessage
+import chat.stoat.persistence.KVStorage
+import kotlinx.coroutines.runBlocking
+import logcat.LogPriority
+import logcat.asLog
+import logcat.logcat
+
+class ReplyReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val channelId = intent.getStringExtra("channelId") ?: run {
+ logcat(LogPriority.ERROR) { "No channel ID, aborting" }
+ return
+ }
+
+ val content = RemoteInput.getResultsFromIntent(intent)
+ ?.getCharSequence("content")
+ ?.toString()
+ ?.takeIf { it.isNotBlank() }
+ ?: run {
+ logcat(LogPriority.ERROR) { "No content, aborting" }
+ return
+ }
+
+ runBlocking {
+ val token = KVStorage(context).get("sessionToken") ?: run {
+ logcat(LogPriority.ERROR) { "No session token, aborting" }
+ return@runBlocking
+ }
+ StoatAPI.setSessionHeader(token)
+ runCatching { sendMessage(channelId, content) }
+ .onFailure { logcat(LogPriority.ERROR) { "Send failed: ${it.asLog()}" } }
+ }
+
+ NotificationManagerCompat.from(context).cancel(channelId, NotificationID.NEW_MESSAGE)
+ }
+}
diff --git a/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt
index 93f68688..bc7b65e3 100644
--- a/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt
+++ b/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt
@@ -80,6 +80,7 @@ import chat.stoat.composables.chat.DisconnectedNotice
import chat.stoat.composables.screens.chat.drawer.ChannelSideDrawer
import chat.stoat.core.model.schemas.ReleaseNotesSettings
import chat.stoat.dialogs.NotificationRationaleDialog
+import chat.stoat.c2dm.NotificationDeepLink
import chat.stoat.internals.extensions.zero
import chat.stoat.persistence.KVStorage
import chat.stoat.screens.chat.dialogs.safety.ReportMessageDialog
@@ -161,8 +162,14 @@ class ChatRouterViewModel(
init {
viewModelScope.launch {
- val current = kvStorage.get("currentDestination")
- setSaveDestination(ChatRouterDestination.fromString(current ?: ""))
+ val pendingChannel = NotificationDeepLink.pendingChannelId.value
+ if (pendingChannel != null) {
+ NotificationDeepLink.pendingChannelId.value = null
+ setSaveDestination(ChatRouterDestination.Channel(pendingChannel))
+ } else {
+ val current = kvStorage.get("currentDestination")
+ setSaveDestination(ChatRouterDestination.fromString(current ?: ""))
+ }
val seenEarlyAccess = kvStorage.getBoolean("spark/earlyAccess/dismissed")
val seenSwipeToReply = kvStorage.getBoolean("spark/swipeToReply/dismissed")
diff --git a/app/src/main/res/drawable/ic_stoat_24dp.xml b/app/src/main/res/drawable/ic_stoat_24dp.xml
new file mode 100644
index 00000000..08fc9c97
--- /dev/null
+++ b/app/src/main/res/drawable/ic_stoat_24dp.xml
@@ -0,0 +1,10 @@
+
+
+