Merge pull request #1884 from vitorpamplona/claude/improve-android-push-notifications-9DzMr

Add direct reply and mark read actions to DM notifications
This commit is contained in:
Vitor Pamplona
2026-03-19 17:08:50 -04:00
committed by GitHub
7 changed files with 449 additions and 133 deletions
+5
View File
@@ -240,6 +240,11 @@
<action android:name="com.shared.NOSTR" />
</intent-filter>
</receiver>
<receiver
android:name=".service.notifications.NotificationReplyReceiver"
android:exported="false" />
</application>
@@ -183,7 +183,7 @@ class EventNotificationConsumer(
}
}
private fun notify(
private suspend fun notify(
event: ChatMessageEncryptedFileHeaderEvent,
account: Account,
) {
@@ -210,13 +210,13 @@ class EventNotificationConsumer(
val content = chatNote.event?.content ?: ""
val user = chatNote.author?.toBestDisplayName() ?: ""
val userPicture = chatNote.author?.profilePicture()
val noteUri =
chatNote.toNEvent() + ACCOUNT_QUERY_PARAM +
account.signer.pubKey
.hexToByteArray()
.toNpub()
val accountNpub =
account.signer.pubKey
.hexToByteArray()
.toNpub()
val chatroomMembers = chatRoom.users.joinToString(",")
val noteUri = chatNote.toNEvent() + ACCOUNT_QUERY_PARAM + accountNpub
// TODO: Show Image on notification
notificationManager()
.sendDMNotification(
event.id,
@@ -226,12 +226,15 @@ class EventNotificationConsumer(
userPicture,
noteUri,
applicationContext,
accountNpub = accountNpub,
accountPictureUrl = account.userProfile().profilePicture(),
chatroomMembers = chatroomMembers,
)
}
}
}
private fun notify(
private suspend fun notify(
event: ChatMessageEvent,
account: Account,
) {
@@ -255,20 +258,25 @@ class EventNotificationConsumer(
val content = chatNote.event?.content ?: ""
val user = chatNote.author?.toBestDisplayName() ?: ""
val userPicture = chatNote.author?.profilePicture()
val noteUri =
chatNote.toNEvent() + ACCOUNT_QUERY_PARAM +
account.signer.pubKey
.hexToByteArray()
.toNpub()
val accountNpub =
account.signer.pubKey
.hexToByteArray()
.toNpub()
val chatroomMembers = chatRoom.users.joinToString(",")
val noteUri = chatNote.toNEvent() + ACCOUNT_QUERY_PARAM + accountNpub
notificationManager()
.sendDMNotification(
event.id,
content,
user,
event.createdAt,
userPicture,
noteUri,
applicationContext,
id = event.id,
messageBody = content,
senderName = user,
time = event.createdAt,
pictureUrl = userPicture,
uri = noteUri,
applicationContext = applicationContext,
accountNpub = accountNpub,
accountPictureUrl = account.userProfile().profilePicture(),
chatroomMembers = chatroomMembers,
)
}
}
@@ -297,13 +305,25 @@ class EventNotificationConsumer(
decryptContent(note, account.signer)?.let { content ->
val user = note.author?.toBestDisplayName() ?: ""
val userPicture = note.author?.profilePicture()
val noteUri =
note.toNEvent() + ACCOUNT_QUERY_PARAM +
account.signer.pubKey
.hexToByteArray()
.toNpub()
val accountNpub =
account.signer.pubKey
.hexToByteArray()
.toNpub()
val noteUri = note.toNEvent() + ACCOUNT_QUERY_PARAM + accountNpub
notificationManager()
.sendDMNotification(event.id, content, user, event.createdAt, userPicture, noteUri, applicationContext)
.sendDMNotification(
id = event.id,
messageBody = content,
senderName = user,
time = event.createdAt,
pictureUrl = userPicture,
uri = noteUri,
applicationContext = applicationContext,
accountNpub = accountNpub,
accountPictureUrl = account.userProfile().profilePicture(),
chatroomMembers = null,
)
}
}
}
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.service.notifications
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.app.RemoteInput
import androidx.core.content.ContextCompat
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlin.coroutines.cancellation.CancellationException
class NotificationReplyReceiver : BroadcastReceiver() {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onReceive(
context: Context,
intent: Intent,
) {
val notificationId = intent.getIntExtra(NotificationUtils.KEY_NOTIFICATION_ID, 0)
val notificationManager =
ContextCompat.getSystemService(context, NotificationManager::class.java)
as NotificationManager
when (intent.action) {
NotificationUtils.MARK_READ_ACTION -> {
notificationManager.cancel(notificationId)
}
NotificationUtils.REPLY_ACTION -> {
val replyText =
RemoteInput
.getResultsFromIntent(intent)
?.getCharSequence(NotificationUtils.KEY_REPLY_TEXT)
?.toString()
if (replyText.isNullOrBlank()) return
val accountNpub = intent.getStringExtra(NotificationUtils.KEY_ACCOUNT_NPUB) ?: return
val chatroomMembersStr = intent.getStringExtra(NotificationUtils.KEY_CHATROOM_MEMBERS) ?: return
val members = chatroomMembersStr.split(",").filter { it.isNotBlank() }
if (members.isEmpty()) return
val pendingResult = goAsync()
scope.launch {
// activates the relay to send the message.
val collectionJob =
scope.launch {
Amethyst.instance.relayProxyClientConnector.relayServices
.collect()
}
try {
sendReply(accountNpub, members, replyText)
notificationManager.cancel(notificationId)
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("NotificationReply", "Failed to send reply: ${e.message}")
} finally {
pendingResult.finish()
// closes the relay connection.
collectionJob.cancel()
}
}
}
}
}
private suspend fun sendReply(
accountNpub: String,
chatroomMembers: List<String>,
replyText: String,
) {
val accountSettings = LocalPreferences.loadAccountConfigFromEncryptedStorage(accountNpub) ?: return
val account = Amethyst.instance.accountsCache.loadAccount(accountSettings)
val recipients = chatroomMembers.map { PTag(it) }
val template = ChatMessageEvent.build(msg = replyText, to = recipients)
account.sendNip17PrivateMessage(template)
}
}
@@ -25,23 +25,37 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
import androidx.core.graphics.drawable.IconCompat
import androidx.core.net.toUri
import coil3.ImageLoader
import coil3.asDrawable
import coil3.executeBlocking
import coil3.request.ImageRequest
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.MainActivity
import com.vitorpamplona.amethyst.ui.stringRes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object NotificationUtils {
private var dmChannel: NotificationChannel? = null
private var zapChannel: NotificationChannel? = null
private const val DM_GROUP_KEY = "com.vitorpamplona.amethyst.DM_NOTIFICATION"
private const val ZAP_GROUP_KEY = "com.vitorpamplona.amethyst.ZAP_NOTIFICATION"
const val REPLY_ACTION = "com.vitorpamplona.amethyst.REPLY_ACTION"
const val MARK_READ_ACTION = "com.vitorpamplona.amethyst.MARK_READ_ACTION"
const val KEY_REPLY_TEXT = "key_reply_text"
const val KEY_NOTIFICATION_ID = "key_notification_id"
const val KEY_ACCOUNT_NPUB = "key_account_npub"
const val KEY_CHATROOM_MEMBERS = "key_chatroom_members"
private const val DM_SUMMARY_ID = 0x10000
private const val ZAP_SUMMARY_ID = 0x20000
fun getOrCreateDMChannel(applicationContext: Context): NotificationChannel {
if (dmChannel != null) return dmChannel!!
@@ -50,13 +64,12 @@ object NotificationUtils {
NotificationChannel(
stringRes(applicationContext, R.string.app_notification_dms_channel_id),
stringRes(applicationContext, R.string.app_notification_dms_channel_name),
NotificationManager.IMPORTANCE_DEFAULT,
NotificationManager.IMPORTANCE_HIGH,
).apply {
description =
stringRes(applicationContext, R.string.app_notification_dms_channel_description)
}
// Register the channel with the system
val notificationManager: NotificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -78,7 +91,6 @@ object NotificationUtils {
stringRes(applicationContext, R.string.app_notification_zaps_channel_description)
}
// Register the channel with the system
val notificationManager: NotificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -87,7 +99,7 @@ object NotificationUtils {
return zapChannel!!
}
fun NotificationManager.sendZapNotification(
suspend fun NotificationManager.sendZapNotification(
id: String,
messageBody: String,
messageTitle: String,
@@ -96,109 +108,109 @@ object NotificationUtils {
uri: String,
applicationContext: Context,
) {
val zapChannel = getOrCreateZapChannel(applicationContext)
getOrCreateZapChannel(applicationContext)
val channelId = stringRes(applicationContext, R.string.app_notification_zaps_channel_id)
sendNotification(
id,
messageBody,
messageTitle,
time,
pictureUrl,
uri,
channelId,
ZAP_GROUP_KEY,
applicationContext,
id = id,
messageBody = messageBody,
messageTitle = messageTitle,
time = time,
pictureUrl = pictureUrl,
uri = uri,
channelId = channelId,
notificationGroupKey = ZAP_GROUP_KEY,
category = NotificationCompat.CATEGORY_SOCIAL,
summaryId = ZAP_SUMMARY_ID,
summaryText = stringRes(applicationContext, R.string.app_notification_zaps_summary),
applicationContext = applicationContext,
)
}
fun NotificationManager.sendDMNotification(
suspend fun NotificationManager.sendDMNotification(
id: String,
messageBody: String,
messageTitle: String,
senderName: String,
time: Long,
pictureUrl: String?,
uri: String,
applicationContext: Context,
accountNpub: String? = null,
accountPictureUrl: String? = null,
chatroomMembers: String? = null,
) {
val dmChannel = getOrCreateDMChannel(applicationContext)
getOrCreateDMChannel(applicationContext)
val channelId = stringRes(applicationContext, R.string.app_notification_dms_channel_id)
sendNotification(
id,
messageBody,
messageTitle,
time,
pictureUrl,
uri,
channelId,
DM_GROUP_KEY,
applicationContext,
sendDMNotificationStyled(
id = id,
messageBody = messageBody,
senderName = senderName,
time = time,
pictureUrl = pictureUrl,
uri = uri,
channelId = channelId,
applicationContext = applicationContext,
accountNpub = accountNpub,
accountPictureUrl = accountPictureUrl,
chatroomMembers = chatroomMembers,
)
}
fun NotificationManager.sendNotification(
private suspend fun loadBitmap(
pictureUrl: String,
applicationContext: Context,
): Bitmap? =
withContext(Dispatchers.IO) {
try {
val request = ImageRequest.Builder(applicationContext).data(pictureUrl).build()
val imageLoader = ImageLoader(applicationContext)
val result = imageLoader.execute(request)
(result.image?.asDrawable(applicationContext.resources) as? BitmapDrawable)?.bitmap
} catch (_: Exception) {
null
}
}
private suspend fun NotificationManager.sendDMNotificationStyled(
id: String,
messageBody: String,
messageTitle: String,
senderName: String,
time: Long,
pictureUrl: String?,
uri: String,
channelId: String,
notificationGroupKey: String,
applicationContext: Context,
) {
if (pictureUrl != null) {
val request = ImageRequest.Builder(applicationContext).data(pictureUrl).build()
val imageLoader = ImageLoader(applicationContext)
val imageResult = imageLoader.executeBlocking(request)
sendNotificationInner(
id = id,
messageBody = messageBody,
messageTitle = messageTitle,
time = time,
picture = imageResult.image?.asDrawable(applicationContext.resources) as? BitmapDrawable,
uri = uri,
channelId,
notificationGroupKey,
applicationContext = applicationContext,
)
} else {
sendNotificationInner(
id = id,
messageBody = messageBody,
messageTitle = messageTitle,
time = time,
picture = null,
uri = uri,
channelId,
notificationGroupKey,
applicationContext = applicationContext,
)
}
}
private fun NotificationManager.sendNotificationInner(
id: String,
messageBody: String,
messageTitle: String,
time: Long,
picture: BitmapDrawable?,
uri: String,
channelId: String,
notificationGroupKey: String,
applicationContext: Context,
accountNpub: String?,
accountPictureUrl: String?,
chatroomMembers: String?,
) {
val notId = id.hashCode()
// dont notify twice
val notifications: Array<StatusBarNotification> = getActiveNotifications()
for (notification in notifications) {
if (notification.id == notId) {
return
}
}
if (isDuplicate(notId)) return
val bitmap = pictureUrl?.let { loadBitmap(it, applicationContext) }
val accountBitmap = accountPictureUrl?.let { loadBitmap(it, applicationContext) }
val senderIcon = bitmap?.let { IconCompat.createWithBitmap(it) }
val accountIcon = accountBitmap?.let { IconCompat.createWithBitmap(it) }
val sender =
Person
.Builder()
.setName(senderName)
.apply { senderIcon?.let { setIcon(it) } }
.build()
val messagingStyle =
NotificationCompat
.MessagingStyle(
Person
.Builder()
.setName("Me")
.setIcon(accountIcon)
.build(),
).addMessage(messageBody, time * 1000, sender)
val contentIntent =
Intent(applicationContext, MainActivity::class.java).apply { data = uri.toUri() }
@@ -208,41 +220,198 @@ object NotificationUtils {
applicationContext,
notId,
contentIntent,
PendingIntent.FLAG_MUTABLE,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
// Build the notification
val builderPublic =
NotificationCompat
.Builder(
applicationContext,
channelId,
).setSmallIcon(R.drawable.amethyst)
.setContentTitle(messageTitle)
.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.amethyst)
.setContentTitle(senderName)
.setContentText(stringRes(applicationContext, R.string.app_notification_private_message))
.setLargeIcon(picture?.bitmap)
.setLargeIcon(bitmap)
.setContentIntent(contentPendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setWhen(time * 1000)
// Build the notification
val builder =
NotificationCompat
.Builder(
applicationContext,
channelId,
).setSmallIcon(R.drawable.amethyst)
.setContentTitle(messageTitle)
.setContentText(messageBody)
.setLargeIcon(picture?.bitmap)
.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.amethyst)
.setLargeIcon(bitmap)
.setStyle(messagingStyle)
.setContentIntent(contentPendingIntent)
.setPublicVersion(builderPublic.build())
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setGroup(DM_GROUP_KEY)
.setAutoCancel(true)
.setWhen(time * 1000)
// Direct Reply action
if (accountNpub != null && chatroomMembers != null) {
val remoteInput =
RemoteInput
.Builder(KEY_REPLY_TEXT)
.setLabel(stringRes(applicationContext, R.string.app_notification_reply_label))
.build()
val replyIntent =
Intent(applicationContext, NotificationReplyReceiver::class.java).apply {
action = REPLY_ACTION
putExtra(KEY_NOTIFICATION_ID, notId)
putExtra(KEY_ACCOUNT_NPUB, accountNpub)
putExtra(KEY_CHATROOM_MEMBERS, chatroomMembers)
}
val replyPendingIntent =
PendingIntent.getBroadcast(
applicationContext,
notId,
replyIntent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
val replyAction =
NotificationCompat.Action
.Builder(R.drawable.amethyst, stringRes(applicationContext, R.string.app_notification_reply_label), replyPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
.build()
builder.addAction(replyAction)
}
// Mark as Read action
val markReadIntent =
Intent(applicationContext, NotificationReplyReceiver::class.java).apply {
action = MARK_READ_ACTION
putExtra(KEY_NOTIFICATION_ID, notId)
}
val markReadPendingIntent =
PendingIntent.getBroadcast(
applicationContext,
notId + 1,
markReadIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
val markReadAction =
NotificationCompat.Action
.Builder(R.drawable.amethyst, stringRes(applicationContext, R.string.app_notification_mark_read_label), markReadPendingIntent)
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
.build()
builder.addAction(markReadAction)
notify(notId, builder.build())
// Group summary notification
sendGroupSummary(channelId, DM_GROUP_KEY, DM_SUMMARY_ID, stringRes(applicationContext, R.string.app_notification_dms_summary), applicationContext)
}
private suspend fun NotificationManager.sendNotification(
id: String,
messageBody: String,
messageTitle: String,
time: Long,
pictureUrl: String?,
uri: String,
channelId: String,
notificationGroupKey: String,
category: String,
summaryId: Int,
summaryText: String,
applicationContext: Context,
) {
val notId = id.hashCode()
if (isDuplicate(notId)) return
val bitmap = pictureUrl?.let { loadBitmap(it, applicationContext) }
val contentIntent =
Intent(applicationContext, MainActivity::class.java).apply { data = uri.toUri() }
val contentPendingIntent =
PendingIntent.getActivity(
applicationContext,
notId,
contentIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
val builderPublic =
NotificationCompat
.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.amethyst)
.setContentTitle(messageTitle)
.setContentText(stringRes(applicationContext, R.string.app_notification_private_message))
.setLargeIcon(bitmap)
.setContentIntent(contentPendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setWhen(time * 1000)
val builder =
NotificationCompat
.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.amethyst)
.setContentTitle(messageTitle)
.setContentText(messageBody)
.setStyle(NotificationCompat.BigTextStyle().bigText(messageBody))
.setLargeIcon(bitmap)
.setContentIntent(contentPendingIntent)
.setPublicVersion(builderPublic.build())
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(category)
.setGroup(notificationGroupKey)
.setAutoCancel(true)
.setWhen(time * 1000)
notify(notId, builder.build())
sendGroupSummary(channelId, notificationGroupKey, summaryId, summaryText, applicationContext)
}
private fun NotificationManager.sendGroupSummary(
channelId: String,
groupKey: String,
summaryId: Int,
summaryText: String,
applicationContext: Context,
) {
val activeCount = activeNotifications.count { it.notification.group == groupKey && it.id != summaryId }
if (activeCount < 2) return
val summaryBuilder =
NotificationCompat
.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.amethyst)
.setGroup(groupKey)
.setGroupSummary(true)
.setAutoCancel(true)
.setStyle(
NotificationCompat
.InboxStyle()
.setSummaryText(summaryText),
)
notify(summaryId, summaryBuilder.build())
}
private fun NotificationManager.isDuplicate(notId: Int): Boolean {
val notifications: Array<StatusBarNotification> = activeNotifications
for (notification in notifications) {
if (notification.id == notId) {
return true
}
}
return false
}
/** Cancels all notifications. */
@@ -428,7 +428,9 @@ private fun NavigateIfIntentRequested(
actionableNextPage?.let { nextRoute ->
val npub = runCatching { URI(intentNextPage.removePrefix("nostr:")).findParameterValue("account") }.getOrNull()
if (npub != null && accountSessionManager.currentAccountNPub() != npub) {
accountSessionManager.checkAndSwitchUserSync(npub, nextRoute)
accountSessionManager.checkAndSwitchUserSync(npub) { account ->
uriToRoute(intentNextPage, account)
}
} else {
val currentRoute = getRouteWithArguments(nextRoute::class, nav.controller)
if (!isSameRoute(currentRoute, nextRoute)) {
@@ -484,7 +486,9 @@ private fun NavigateIfIntentRequested(
scope.launch {
val npub = runCatching { URI(uri.removePrefix("nostr:")).findParameterValue("account") }.getOrNull()
if (npub != null && accountSessionManager.currentAccountNPub() != npub) {
accountSessionManager.checkAndSwitchUserSync(npub, newPage)
accountSessionManager.checkAndSwitchUserSync(npub) { newAccount ->
uriToRoute(uri, newAccount)
}
} else {
val currentRoute = getRouteWithArguments(newPage::class, nav.controller)
if (!isSameRoute(currentRoute, newPage)) {
@@ -112,11 +112,11 @@ class AccountSessionManager(
}
}
private suspend fun loginWithDefaultAccount(route: Route? = null) {
private suspend fun loginWithDefaultAccount(routeBuilder: ((account: Account) -> Route?)? = null) {
val accountSettings = localPreferences.loadAccountConfigFromEncryptedStorage()
if (accountSettings != null) {
startUI(accountSettings, route)
startUI(accountSettings, routeBuilder)
} else {
requestLoginUI()
}
@@ -184,11 +184,11 @@ class AccountSessionManager(
fun startUI(
accountSettings: AccountSettings,
route: Route? = null,
routeBuilder: ((account: Account) -> Route?)? = null,
) {
val account = accountsCache.loadAccount(accountSettings)
_accountContent.update {
AccountState.LoggedIn(account, route)
AccountState.LoggedIn(account, routeBuilder?.invoke(account))
}
}
@@ -280,7 +280,7 @@ class AccountSessionManager(
localPreferences.setDefaultAccount(accountSettings)
startUI(accountSettings, route = Route.ImportFollowsSelectUser)
startUI(accountSettings, routeBuilder = { Route.ImportFollowsSelectUser })
scope.launch(Dispatchers.IO) {
delay(2000) // waits for the new user to connect to the new relays.
@@ -329,12 +329,12 @@ class AccountSessionManager(
suspend fun checkAndSwitchUserSync(
npub: String,
route: Route,
routeBuilder: ((account: Account) -> Route?)? = null,
): Boolean {
if (npub != localPreferences.currentAccount()) {
val account = localPreferences.allSavedAccounts().firstOrNull { it.npub == npub }
if (account != null) {
switchUserSync(account, route)
switchUserSync(account, routeBuilder)
return true
}
}
@@ -343,10 +343,10 @@ class AccountSessionManager(
private suspend fun switchUserSync(
accountInfo: AccountInfo,
route: Route? = null,
routeBuilder: ((account: Account) -> Route?)? = null,
) {
localPreferences.switchToAccount(accountInfo)
loginWithDefaultAccount(route)
loginWithDefaultAccount(routeBuilder)
}
fun currentAccountNPub() =
+5
View File
@@ -732,6 +732,11 @@
<string name="app_notification_zaps_channel_message_from">From %1$s</string>
<string name="app_notification_zaps_channel_message_for">for %1$s</string>
<string name="app_notification_reply_label">Reply</string>
<string name="app_notification_mark_read_label">Mark Read</string>
<string name="app_notification_dms_summary">New messages</string>
<string name="app_notification_zaps_summary">New zaps</string>
<string name="reply_notify">Notify: </string>
<string name="channel_list_join_conversation">Join Conversation</string>