mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
Merge pull request #3310 from vitorpamplona/claude/android-tray-dismiss-vbdhve
Auto-dismiss notifications when events are read in-app
This commit is contained in:
+33
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.notifications
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
@@ -43,6 +44,7 @@ import coil3.request.allowHardware
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -791,4 +793,35 @@ object NotificationUtils {
|
||||
fun NotificationManager.cancelNotifications() {
|
||||
cancelAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the tray notification posted for [eventId] — used to auto-clear a
|
||||
* notification once the user reads the underlying event in-app.
|
||||
*
|
||||
* Per-event notifications are keyed by `id.hashCode()` (see [sendNotification]
|
||||
* and [sendDMNotificationStyled]), so hashing the same event id targets exactly
|
||||
* the notification posted for it. Cancelling an id that isn't currently shown is
|
||||
* a harmless no-op. After removing the child, any group summary left without
|
||||
* children is cancelled too so the tray doesn't keep an empty summary around.
|
||||
*/
|
||||
fun NotificationManager.dismissNotificationForEvent(eventId: HexKey) {
|
||||
val notId = eventId.hashCode()
|
||||
|
||||
// Most events the user reads never had a tray notification (regular feed
|
||||
// items), so bail out before touching anything when nothing is posted for it.
|
||||
if (activeNotifications.none { it.id == notId }) return
|
||||
|
||||
cancel(notId)
|
||||
cancelChildlessGroupSummaries()
|
||||
}
|
||||
|
||||
private fun NotificationManager.cancelChildlessGroupSummaries() {
|
||||
val active = activeNotifications
|
||||
for (summary in active) {
|
||||
if (summary.notification.flags and Notification.FLAG_GROUP_SUMMARY == 0) continue
|
||||
val group = summary.notification.group ?: continue
|
||||
val hasChildren = active.any { it.id != summary.id && it.notification.group == group }
|
||||
if (!hasChildren) cancel(summary.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@ import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.roadstr.confirmation.RoadEventConfirmationEvent
|
||||
import com.vitorpamplona.quartz.experimental.roadstr.report.RoadEventReportEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geoHashOrScope
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
@@ -528,6 +529,7 @@ fun calculateBackgroundColor(
|
||||
routeForLastRead: String? = null,
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
dismissNotificationId: HexKey? = null,
|
||||
): MutableState<Color> {
|
||||
val defaultBackgroundColor = MaterialTheme.colorScheme.background
|
||||
val newItemColor = MaterialTheme.colorScheme.newItemBackgroundColor
|
||||
@@ -540,7 +542,7 @@ fun calculateBackgroundColor(
|
||||
|
||||
val isNew =
|
||||
remember(createdAt, routeForLastRead) {
|
||||
routeForLastRead != null && accountViewModel.loadAndMarkAsRead(routeForLastRead, createdAt)
|
||||
routeForLastRead != null && accountViewModel.loadAndMarkAsRead(routeForLastRead, createdAt, dismissNotificationId)
|
||||
}
|
||||
|
||||
val bgColor =
|
||||
@@ -589,6 +591,7 @@ private fun CheckNewAndRenderNote(
|
||||
routeForLastRead,
|
||||
parentBackgroundColor,
|
||||
accountViewModel,
|
||||
dismissNotificationId = baseNote.idHex,
|
||||
)
|
||||
|
||||
InnerNoteWithReactions(
|
||||
|
||||
+13
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -30,6 +31,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -69,6 +71,7 @@ import com.vitorpamplona.amethyst.service.cashu.melt.MeltProcessor
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.dismissNotificationForEvent
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
||||
import com.vitorpamplona.amethyst.ui.actions.Dao
|
||||
@@ -1609,6 +1612,7 @@ class AccountViewModel(
|
||||
fun loadAndMarkAsRead(
|
||||
routeForLastRead: String,
|
||||
createdAt: Long?,
|
||||
dismissNotificationId: HexKey? = null,
|
||||
): Boolean {
|
||||
if (createdAt == null) return false
|
||||
|
||||
@@ -1619,12 +1623,21 @@ class AccountViewModel(
|
||||
if (onIsNew) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
account.markAsRead(routeForLastRead, createdAt)
|
||||
// The user is now looking at this event in-app, so clear any tray
|
||||
// notification that was posted for it while the app was backgrounded.
|
||||
dismissNotificationId?.let { dismissTrayNotificationFor(it) }
|
||||
}
|
||||
}
|
||||
|
||||
return onIsNew
|
||||
}
|
||||
|
||||
private fun dismissTrayNotificationFor(eventId: HexKey) {
|
||||
ContextCompat
|
||||
.getSystemService(Amethyst.instance.appContext, NotificationManager::class.java)
|
||||
?.dismissNotificationForEvent(eventId)
|
||||
}
|
||||
|
||||
fun markAllChatNotesAsRead(notes: List<Note>) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
for (note in notes) {
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ fun NormalChatNote(
|
||||
|
||||
if (routeForLastRead != null) {
|
||||
LaunchedEffect(key1 = routeForLastRead) {
|
||||
accountViewModel.loadAndMarkAsRead(routeForLastRead, note.createdAt())
|
||||
accountViewModel.loadAndMarkAsRead(routeForLastRead, note.createdAt(), dismissNotificationId = note.idHex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user