mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-09 23:22:35 +02:00
Support for Push Notifications in the PlayStore build
This commit is contained in:
@@ -2,6 +2,7 @@ plugins {
|
|||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
id 'org.jlleitschuh.gradle.ktlint' version "11.3.1"
|
id 'org.jlleitschuh.gradle.ktlint' version "11.3.1"
|
||||||
|
id 'com.google.gms.google-services'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -172,6 +173,10 @@ dependencies {
|
|||||||
// Google services model the translate text
|
// Google services model the translate text
|
||||||
playImplementation 'com.google.mlkit:translate:17.0.1'
|
playImplementation 'com.google.mlkit:translate:17.0.1'
|
||||||
|
|
||||||
|
// PushNotifications
|
||||||
|
playImplementation platform('com.google.firebase:firebase-bom:32.0.0')
|
||||||
|
playImplementation 'com.google.firebase:firebase-messaging-ktx'
|
||||||
|
|
||||||
// Automatic memory leak detection
|
// Automatic memory leak detection
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
||||||
|
|
||||||
|
68
app/google-services.json
Normal file
68
app/google-services.json
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "768341258853",
|
||||||
|
"project_id": "amethyst-3057a",
|
||||||
|
"storage_bucket": "amethyst-3057a.appspot.com"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:768341258853:android:5d07c35a37b24ff36b8c8c",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.vitorpamplona.amethyst"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyB7ZxgdpgrN6R223HCFdfv4ulP8Egp7trE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:768341258853:android:e0200680f552484d6b8c8c",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.vitorpamplona.amethyst.debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyB7ZxgdpgrN6R223HCFdfv4ulP8Egp7trE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
|
|
||||||
|
class PushNotificationUtils {
|
||||||
|
fun init(accounts: List<AccountInfo>) {
|
||||||
|
}
|
||||||
|
}
|
@@ -70,6 +70,7 @@
|
|||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
android:screenOrientation="fullSensor"
|
android:screenOrientation="fullSensor"
|
||||||
tools:replace="screenOrientation" />
|
tools:replace="screenOrientation" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@@ -213,7 +213,13 @@ object LocalPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadFromEncryptedStorage(): Account? {
|
fun loadFromEncryptedStorage(): Account? {
|
||||||
encryptedPreferences(currentAccount()).apply {
|
val acc = loadFromEncryptedStorage(currentAccount())
|
||||||
|
acc?.registerObservers()
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadFromEncryptedStorage(npub: String?): Account? {
|
||||||
|
encryptedPreferences(npub).apply {
|
||||||
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return null
|
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return null
|
||||||
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
|
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
|
||||||
val followingChannels = getStringSet(PrefKeys.FOLLOWING_CHANNELS, null) ?: setOf()
|
val followingChannels = getStringSet(PrefKeys.FOLLOWING_CHANNELS, null) ?: setOf()
|
||||||
|
@@ -1037,14 +1037,7 @@ class Account(
|
|||||||
saveable.invalidateData()
|
saveable.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
fun registerObservers() {
|
||||||
backupContactList?.let {
|
|
||||||
println("Loading saved contacts ${it.toJson()}")
|
|
||||||
if (userProfile().latestContactList == null) {
|
|
||||||
LocalCache.consume(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observes relays to restart connections
|
// Observes relays to restart connections
|
||||||
userProfile().live().relays.observeForever {
|
userProfile().live().relays.observeForever {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
@@ -1071,6 +1064,15 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
backupContactList?.let {
|
||||||
|
println("Loading saved contacts ${it.toJson()}")
|
||||||
|
if (userProfile().latestContactList == null) {
|
||||||
|
LocalCache.consume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountLiveData(private val account: Account) : LiveData<AccountState>(AccountState(account)) {
|
class AccountLiveData(private val account: Account) : LiveData<AccountState>(AccountState(account)) {
|
||||||
|
@@ -347,7 +347,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: PrivateDmEvent, relay: Relay?) {
|
fun consume(event: PrivateDmEvent, relay: Relay?): Note {
|
||||||
val note = getOrCreateNote(event.id)
|
val note = getOrCreateNote(event.id)
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return note
|
||||||
|
|
||||||
val recipient = event.verifiedRecipientPubKey()?.let { getOrCreateUser(it) }
|
val recipient = event.verifiedRecipientPubKey()?.let { getOrCreateUser(it) }
|
||||||
|
|
||||||
@@ -374,6 +374,8 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
|
|
||||||
|
return note
|
||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: DeletionEvent) {
|
fun consume(event: DeletionEvent) {
|
||||||
@@ -911,6 +913,60 @@ object LocalCache {
|
|||||||
private fun refreshObservers(newNote: Note) {
|
private fun refreshObservers(newNote: Note) {
|
||||||
live.invalidateData(newNote)
|
live.invalidateData(newNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun consume(event: Event, relay: Relay?) {
|
||||||
|
if (!event.hasValidSignature()) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
when (event) {
|
||||||
|
is BadgeAwardEvent -> consume(event)
|
||||||
|
is BadgeDefinitionEvent -> consume(event)
|
||||||
|
is BadgeProfilesEvent -> consume(event)
|
||||||
|
is BookmarkListEvent -> consume(event)
|
||||||
|
is ChannelCreateEvent -> consume(event)
|
||||||
|
is ChannelHideMessageEvent -> consume(event)
|
||||||
|
is ChannelMessageEvent -> consume(event, relay)
|
||||||
|
is ChannelMetadataEvent -> consume(event)
|
||||||
|
is ChannelMuteUserEvent -> consume(event)
|
||||||
|
is ContactListEvent -> consume(event)
|
||||||
|
is DeletionEvent -> consume(event)
|
||||||
|
|
||||||
|
is FileHeaderEvent -> consume(event, relay)
|
||||||
|
is FileStorageEvent -> consume(event, relay)
|
||||||
|
is FileStorageHeaderEvent -> consume(event, relay)
|
||||||
|
is HighlightEvent -> consume(event, relay)
|
||||||
|
is LnZapEvent -> {
|
||||||
|
event.zapRequest?.let {
|
||||||
|
consume(it, relay)
|
||||||
|
}
|
||||||
|
consume(event)
|
||||||
|
}
|
||||||
|
is LnZapRequestEvent -> consume(event)
|
||||||
|
is LnZapPaymentRequestEvent -> consume(event)
|
||||||
|
is LnZapPaymentResponseEvent -> consume(event)
|
||||||
|
is LongTextNoteEvent -> consume(event, relay)
|
||||||
|
is MetadataEvent -> consume(event)
|
||||||
|
is PrivateDmEvent -> consume(event, relay)
|
||||||
|
is PeopleListEvent -> consume(event)
|
||||||
|
is ReactionEvent -> consume(event)
|
||||||
|
is RecommendRelayEvent -> consume(event)
|
||||||
|
is ReportEvent -> consume(event, relay)
|
||||||
|
is RepostEvent -> {
|
||||||
|
event.containedPost()?.let {
|
||||||
|
consume(it, relay)
|
||||||
|
}
|
||||||
|
consume(event)
|
||||||
|
}
|
||||||
|
is TextNoteEvent -> consume(event, relay)
|
||||||
|
is PollNoteEvent -> consume(event, relay)
|
||||||
|
else -> {
|
||||||
|
Log.w("Event Not Supported", event.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalCacheLiveData : LiveData<Set<Note>>(setOf<Note>()) {
|
class LocalCacheLiveData : LiveData<Set<Note>>(setOf<Note>()) {
|
||||||
|
@@ -3,28 +3,7 @@ package com.vitorpamplona.amethyst.service
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.BookmarkListEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelHideMessageEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMuteUserEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.DeletionEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.MetadataEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.RecommendRelayEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.service.relays.Subscription
|
import com.vitorpamplona.amethyst.service.relays.Subscription
|
||||||
@@ -52,8 +31,6 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
private val clientListener = object : Client.Listener() {
|
private val clientListener = object : Client.Listener() {
|
||||||
override fun onEvent(event: Event, subscriptionId: String, relay: Relay) {
|
override fun onEvent(event: Event, subscriptionId: String, relay: Relay) {
|
||||||
if (subscriptionId in subscriptions.keys) {
|
if (subscriptionId in subscriptions.keys) {
|
||||||
if (!event.hasValidSignature()) return
|
|
||||||
|
|
||||||
val key = "$debugName $subscriptionId ${event.kind}"
|
val key = "$debugName $subscriptionId ${event.kind}"
|
||||||
val keyValue = eventCounter.get(key)
|
val keyValue = eventCounter.get(key)
|
||||||
if (keyValue != null) {
|
if (keyValue != null) {
|
||||||
@@ -62,51 +39,7 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
eventCounter = eventCounter + Pair(key, Counter(1))
|
eventCounter = eventCounter + Pair(key, Counter(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
LocalCache.consume(event, relay)
|
||||||
when (event) {
|
|
||||||
is BadgeAwardEvent -> LocalCache.consume(event)
|
|
||||||
is BadgeDefinitionEvent -> LocalCache.consume(event)
|
|
||||||
is BadgeProfilesEvent -> LocalCache.consume(event)
|
|
||||||
is BookmarkListEvent -> LocalCache.consume(event)
|
|
||||||
is ChannelCreateEvent -> LocalCache.consume(event)
|
|
||||||
is ChannelHideMessageEvent -> LocalCache.consume(event)
|
|
||||||
is ChannelMessageEvent -> LocalCache.consume(event, relay)
|
|
||||||
is ChannelMetadataEvent -> LocalCache.consume(event)
|
|
||||||
is ChannelMuteUserEvent -> LocalCache.consume(event)
|
|
||||||
is ContactListEvent -> LocalCache.consume(event)
|
|
||||||
is DeletionEvent -> LocalCache.consume(event)
|
|
||||||
|
|
||||||
is FileHeaderEvent -> LocalCache.consume(event, relay)
|
|
||||||
is FileStorageEvent -> LocalCache.consume(event, relay)
|
|
||||||
is FileStorageHeaderEvent -> LocalCache.consume(event, relay)
|
|
||||||
is HighlightEvent -> LocalCache.consume(event, relay)
|
|
||||||
is LnZapEvent -> {
|
|
||||||
event.zapRequest?.let { onEvent(it, subscriptionId, relay) }
|
|
||||||
LocalCache.consume(event)
|
|
||||||
}
|
|
||||||
is LnZapRequestEvent -> LocalCache.consume(event)
|
|
||||||
is LnZapPaymentRequestEvent -> LocalCache.consume(event)
|
|
||||||
is LnZapPaymentResponseEvent -> LocalCache.consume(event)
|
|
||||||
is LongTextNoteEvent -> LocalCache.consume(event, relay)
|
|
||||||
is MetadataEvent -> LocalCache.consume(event)
|
|
||||||
is PrivateDmEvent -> LocalCache.consume(event, relay)
|
|
||||||
is PeopleListEvent -> LocalCache.consume(event)
|
|
||||||
is ReactionEvent -> LocalCache.consume(event)
|
|
||||||
is RecommendRelayEvent -> LocalCache.consume(event)
|
|
||||||
is ReportEvent -> LocalCache.consume(event, relay)
|
|
||||||
is RepostEvent -> {
|
|
||||||
event.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
|
||||||
LocalCache.consume(event)
|
|
||||||
}
|
|
||||||
is TextNoteEvent -> LocalCache.consume(event, relay)
|
|
||||||
is PollNoteEvent -> LocalCache.consume(event, relay)
|
|
||||||
else -> {
|
|
||||||
Log.w("Event Not Supported", event.toJson())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ class PrivateDmEvent(
|
|||||||
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
|
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
|
||||||
* for initial messages.
|
* for initial messages.
|
||||||
*/
|
*/
|
||||||
fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }
|
private fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }
|
||||||
|
|
||||||
fun recipientPubKeyBytes() = recipientPubKey()?.runCatching { Hex.decode(this[1]) }?.getOrNull()
|
fun recipientPubKeyBytes() = recipientPubKey()?.runCatching { Hex.decode(this[1]) }?.getOrNull()
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ object Nip19 {
|
|||||||
val hex: String,
|
val hex: String,
|
||||||
val relay: String? = null,
|
val relay: String? = null,
|
||||||
val author: String? = null,
|
val author: String? = null,
|
||||||
val kind: Long? = null,
|
val kind: Int? = null,
|
||||||
val additionalChars: String = ""
|
val additionalChars: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ object Nip19 {
|
|||||||
|
|
||||||
val kind = tlv.get(Tlv.Type.KIND.id)
|
val kind = tlv.get(Tlv.Type.KIND.id)
|
||||||
?.get(0)
|
?.get(0)
|
||||||
?.let { Tlv.toInt32(it) }?.toLong()
|
?.let { Tlv.toInt32(it) }
|
||||||
|
|
||||||
return Return(Type.EVENT, hex, relay, author, kind)
|
return Return(Type.EVENT, hex, relay, author, kind)
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ object Nip19 {
|
|||||||
|
|
||||||
val kind = tlv.get(Tlv.Type.KIND.id)
|
val kind = tlv.get(Tlv.Type.KIND.id)
|
||||||
?.get(0)
|
?.get(0)
|
||||||
?.let { Tlv.toInt32(it) }?.toLong()
|
?.let { Tlv.toInt32(it) }
|
||||||
|
|
||||||
return Return(Type.ADDRESS, "$kind:$author:$d", relay, author, kind)
|
return Return(Type.ADDRESS, "$kind:$author:$d", relay, author, kind)
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,97 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendDMNotification
|
||||||
|
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||||
|
|
||||||
|
class EventNotificationConsumer(private val applicationContext: Context) {
|
||||||
|
fun consume(event: Event) {
|
||||||
|
// adds to database
|
||||||
|
LocalCache.consume(event, null)
|
||||||
|
|
||||||
|
when (event) {
|
||||||
|
is PrivateDmEvent -> notify(event)
|
||||||
|
is LnZapEvent -> notify(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notify(event: PrivateDmEvent) {
|
||||||
|
val note = LocalCache.notes[event.id] ?: return
|
||||||
|
|
||||||
|
LocalPreferences.allSavedAccounts().forEach {
|
||||||
|
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||||
|
|
||||||
|
if (acc != null && acc.userProfile().pubkeyHex == event.verifiedRecipientPubKey()) {
|
||||||
|
val followingKeySet = acc.followingKeySet()
|
||||||
|
|
||||||
|
val messagingWith = acc.userProfile().privateChatrooms.keys.filter {
|
||||||
|
(
|
||||||
|
it.pubkeyHex in followingKeySet || acc.userProfile()
|
||||||
|
.hasSentMessagesTo(it)
|
||||||
|
) && !acc.isHidden(it)
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
if (note.author in messagingWith) {
|
||||||
|
val content = acc.decryptContent(note) ?: ""
|
||||||
|
val user = note.author?.toBestDisplayName() ?: ""
|
||||||
|
val userPicture = note.author?.profilePicture()
|
||||||
|
val noteUri = note.toNEvent()
|
||||||
|
notificationManager().sendDMNotification(content, user, userPicture, noteUri, applicationContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notify(event: LnZapEvent) {
|
||||||
|
val noteZapEvent = LocalCache.notes[event.id] ?: return
|
||||||
|
val noteZapRequest = event.zapRequest?.id?.let { LocalCache.checkGetOrCreateNote(it) }
|
||||||
|
val noteZapped = event.zappedPost().firstOrNull()?.let { LocalCache.checkGetOrCreateNote(it) }
|
||||||
|
|
||||||
|
LocalPreferences.allSavedAccounts().forEach {
|
||||||
|
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||||
|
|
||||||
|
if (acc != null && acc.userProfile().pubkeyHex == event.zappedAuthor().firstOrNull()) {
|
||||||
|
val amount = showAmount(event.amount)
|
||||||
|
val senderInfo = (noteZapRequest?.event as? LnZapRequestEvent)?.let {
|
||||||
|
val decryptedContent = acc.decryptZapContentAuthor(noteZapRequest)
|
||||||
|
if (decryptedContent != null) {
|
||||||
|
val author = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
||||||
|
Pair(author, decryptedContent.content)
|
||||||
|
} else if (!noteZapRequest.event?.content().isNullOrBlank()) {
|
||||||
|
Pair(noteZapRequest.author, noteZapRequest.event?.content())
|
||||||
|
} else {
|
||||||
|
Pair(noteZapRequest.author, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val zappedContent = noteZapped?.event?.content()?.split("\n")?.get(0)?.take(50)
|
||||||
|
val user = senderInfo?.first?.toBestDisplayName() ?: ""
|
||||||
|
var title = applicationContext.getString(R.string.app_notification_zaps_channel_message, amount)
|
||||||
|
senderInfo?.second?.let {
|
||||||
|
title += " ($it)"
|
||||||
|
}
|
||||||
|
var content = applicationContext.getString(R.string.app_notification_zaps_channel_message_from, user)
|
||||||
|
zappedContent?.let {
|
||||||
|
content += " " + applicationContext.getString(R.string.app_notification_zaps_channel_message_for, zappedContent)
|
||||||
|
}
|
||||||
|
val userPicture = senderInfo?.first?.profilePicture()
|
||||||
|
val noteUri = "nostr:Notifications"
|
||||||
|
notificationManager().sendZapNotification(content, title, userPicture, noteUri, applicationContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notificationManager(): NotificationManager {
|
||||||
|
return ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,185 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.executeBlocking
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||||
|
|
||||||
|
object NotificationUtils {
|
||||||
|
|
||||||
|
// Notification ID.
|
||||||
|
private var notificationId = 0
|
||||||
|
|
||||||
|
private var dmChannel: NotificationChannel? = null
|
||||||
|
private var zapChannel: NotificationChannel? = null
|
||||||
|
|
||||||
|
private fun getOrCreateDMChannel(applicationContext: Context): NotificationChannel {
|
||||||
|
if (dmChannel != null) return dmChannel!!
|
||||||
|
|
||||||
|
dmChannel = NotificationChannel(
|
||||||
|
applicationContext.getString(R.string.app_notification_dms_channel_id),
|
||||||
|
applicationContext.getString(R.string.app_notification_dms_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
description = applicationContext.getString(R.string.app_notification_dms_channel_description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the channel with the system
|
||||||
|
val notificationManager: NotificationManager =
|
||||||
|
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(dmChannel!!)
|
||||||
|
|
||||||
|
return dmChannel!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrCreateZapChannel(applicationContext: Context): NotificationChannel {
|
||||||
|
if (zapChannel != null) return zapChannel!!
|
||||||
|
|
||||||
|
zapChannel = NotificationChannel(
|
||||||
|
applicationContext.getString(R.string.app_notification_zaps_channel_id),
|
||||||
|
applicationContext.getString(R.string.app_notification_zaps_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
description = applicationContext.getString(R.string.app_notification_zaps_channel_description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the channel with the system
|
||||||
|
val notificationManager: NotificationManager =
|
||||||
|
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(zapChannel!!)
|
||||||
|
|
||||||
|
return zapChannel!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NotificationManager.sendZapNotification(
|
||||||
|
messageBody: String,
|
||||||
|
messageTitle: String,
|
||||||
|
pictureUrl: String?,
|
||||||
|
uri: String,
|
||||||
|
applicationContext: Context
|
||||||
|
) {
|
||||||
|
val zapChannel = getOrCreateZapChannel(applicationContext)
|
||||||
|
val channelId = applicationContext.getString(R.string.app_notification_zaps_channel_id)
|
||||||
|
|
||||||
|
sendNotification(messageBody, messageTitle, pictureUrl, uri, channelId, applicationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NotificationManager.sendDMNotification(
|
||||||
|
messageBody: String,
|
||||||
|
messageTitle: String,
|
||||||
|
pictureUrl: String?,
|
||||||
|
uri: String,
|
||||||
|
applicationContext: Context
|
||||||
|
) {
|
||||||
|
val dmChannel = getOrCreateDMChannel(applicationContext)
|
||||||
|
val channelId = applicationContext.getString(R.string.app_notification_dms_channel_id)
|
||||||
|
|
||||||
|
sendNotification(messageBody, messageTitle, pictureUrl, uri, channelId, applicationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NotificationManager.sendNotification(
|
||||||
|
messageBody: String,
|
||||||
|
messageTitle: String,
|
||||||
|
pictureUrl: String?,
|
||||||
|
uri: String,
|
||||||
|
channelId: String,
|
||||||
|
applicationContext: Context
|
||||||
|
) {
|
||||||
|
if (pictureUrl != null) {
|
||||||
|
val request = ImageRequest.Builder(applicationContext)
|
||||||
|
.data(pictureUrl)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val imageLoader = ImageLoader(applicationContext)
|
||||||
|
val imageResult = imageLoader.executeBlocking(request)
|
||||||
|
sendNotification(
|
||||||
|
messageBody = messageBody,
|
||||||
|
messageTitle = messageTitle,
|
||||||
|
picture = imageResult.drawable as? BitmapDrawable,
|
||||||
|
uri = uri,
|
||||||
|
channelId,
|
||||||
|
applicationContext = applicationContext
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
sendNotification(
|
||||||
|
messageBody = messageBody,
|
||||||
|
messageTitle = messageTitle,
|
||||||
|
picture = null,
|
||||||
|
uri = uri,
|
||||||
|
channelId,
|
||||||
|
applicationContext = applicationContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NotificationManager.sendNotification(
|
||||||
|
messageBody: String,
|
||||||
|
messageTitle: String,
|
||||||
|
picture: BitmapDrawable?,
|
||||||
|
uri: String,
|
||||||
|
channelId: String,
|
||||||
|
applicationContext: Context
|
||||||
|
) {
|
||||||
|
val contentIntent = Intent(applicationContext, MainActivity::class.java).apply {
|
||||||
|
data = Uri.parse(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentPendingIntent = PendingIntent.getActivity(
|
||||||
|
applicationContext,
|
||||||
|
notificationId,
|
||||||
|
contentIntent,
|
||||||
|
PendingIntent.FLAG_MUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build the notification
|
||||||
|
val builderPublic = NotificationCompat.Builder(
|
||||||
|
applicationContext,
|
||||||
|
channelId
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.amethyst)
|
||||||
|
.setContentTitle(messageTitle)
|
||||||
|
.setContentText(applicationContext.getString(R.string.app_notification_private_message))
|
||||||
|
.setLargeIcon(picture?.bitmap)
|
||||||
|
.setGroup(messageTitle)
|
||||||
|
.setContentIntent(contentPendingIntent)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
|
||||||
|
// Build the notification
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
applicationContext,
|
||||||
|
channelId
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.amethyst)
|
||||||
|
.setContentTitle(messageTitle)
|
||||||
|
.setContentText(messageBody)
|
||||||
|
.setLargeIcon(picture?.bitmap)
|
||||||
|
.setGroup(messageTitle)
|
||||||
|
.setContentIntent(contentPendingIntent)
|
||||||
|
.setPublicVersion(builderPublic.build())
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
|
||||||
|
notify(notificationId, builder.build())
|
||||||
|
|
||||||
|
notificationId++
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels all notifications.
|
||||||
|
*/
|
||||||
|
fun NotificationManager.cancelNotifications() {
|
||||||
|
cancelAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
|
import com.vitorpamplona.amethyst.service.model.RelayAuthEvent
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
|
class RegisterAccounts(
|
||||||
|
private val accounts: List<AccountInfo>
|
||||||
|
) {
|
||||||
|
|
||||||
|
// creates proof that it controls all accounts
|
||||||
|
private fun signEventsToProveControlOfAccounts(
|
||||||
|
accounts: List<AccountInfo>,
|
||||||
|
notificationToken: String
|
||||||
|
): List<RelayAuthEvent> {
|
||||||
|
return accounts.mapNotNull {
|
||||||
|
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
|
||||||
|
if (acc != null) {
|
||||||
|
val relayToUse = acc.activeRelays()?.firstOrNull { it.read }
|
||||||
|
if (relayToUse != null) {
|
||||||
|
acc.createAuthEvent(relayToUse, notificationToken)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postRegistrationEvent(events: List<RelayAuthEvent>) {
|
||||||
|
try {
|
||||||
|
val jsonObject = """{
|
||||||
|
"events": [ ${events.joinToString(", ") { it.toJson() }} ]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
|
val body = jsonObject.toRequestBody(mediaType)
|
||||||
|
|
||||||
|
val request = Request.Builder()
|
||||||
|
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||||
|
.url("https://push.amethyst.social/register")
|
||||||
|
.post(body)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val client = OkHttpClient.Builder().build()
|
||||||
|
|
||||||
|
client.newCall(request).execute()
|
||||||
|
} catch (e: java.lang.Exception) {
|
||||||
|
Log.e("FirebaseMsgService", "Unable to register with push server", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun go(notificationToken: String) {
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
scope.launch {
|
||||||
|
postRegistrationEvent(
|
||||||
|
signEventsToProveControlOfAccounts(accounts, notificationToken)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,12 @@ import coil.decode.SvgDecoder
|
|||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.ServiceManager
|
import com.vitorpamplona.amethyst.ServiceManager
|
||||||
import com.vitorpamplona.amethyst.VideoCache
|
import com.vitorpamplona.amethyst.VideoCache
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||||
|
import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import com.vitorpamplona.amethyst.ui.components.muted
|
import com.vitorpamplona.amethyst.ui.components.muted
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
@@ -39,15 +44,30 @@ class MainActivity : FragmentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val nip19 = Nip19.uriToRoute(intent?.data?.toString())
|
val uri = intent?.data?.toString()
|
||||||
val startingPage = when (nip19?.type) {
|
|
||||||
Nip19.Type.USER -> "User/${nip19.hex}"
|
val startingPage = if (uri.equals("nostr:Notifications", true)) {
|
||||||
Nip19.Type.NOTE -> "Note/${nip19.hex}"
|
Route.Notification.route.replace("{scrollToTop}", "true")
|
||||||
Nip19.Type.EVENT -> "Event/${nip19.hex}"
|
} else {
|
||||||
Nip19.Type.ADDRESS -> "Note/${nip19.hex}"
|
val nip19 = Nip19.uriToRoute(uri)
|
||||||
else -> null
|
when (nip19?.type) {
|
||||||
|
Nip19.Type.USER -> "User/${nip19.hex}"
|
||||||
|
Nip19.Type.NOTE -> "Note/${nip19.hex}"
|
||||||
|
Nip19.Type.EVENT -> {
|
||||||
|
if (nip19.kind == PrivateDmEvent.kind) {
|
||||||
|
"Room/${nip19.author}"
|
||||||
|
} else if (nip19.kind == ChannelMessageEvent.kind || nip19.kind == ChannelCreateEvent.kind || nip19.kind == ChannelMetadataEvent.kind) {
|
||||||
|
"Channel/${nip19.hex}"
|
||||||
|
} else {
|
||||||
|
"Event/${nip19.hex}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Nip19.Type.ADDRESS -> "Note/${nip19.hex}"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
} ?: try {
|
} ?: try {
|
||||||
intent?.data?.toString()?.let {
|
uri?.let {
|
||||||
Nip47.parse(it)
|
Nip47.parse(it)
|
||||||
val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString())
|
val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString())
|
||||||
Route.Home.base + "?nip47=" + encodedUri
|
Route.Home.base + "?nip47=" + encodedUri
|
||||||
@@ -100,6 +120,8 @@ class MainActivity : FragmentActivity() {
|
|||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
ServiceManager.start()
|
ServiceManager.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PushNotificationUtils().init(LocalPreferences.allSavedAccounts())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
@@ -357,9 +357,21 @@
|
|||||||
<string name="upload_server_relays_nip95_explainer">Files are hosted by your relays. New NIP: check if they support</string>
|
<string name="upload_server_relays_nip95_explainer">Files are hosted by your relays. New NIP: check if they support</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<string name="follow_list_selection">Follow List</string>
|
<string name="follow_list_selection">Follow List</string>
|
||||||
<string name="follow_list_kind3follows">All Follows</string>
|
<string name="follow_list_kind3follows">All Follows</string>
|
||||||
<string name="follow_list_global">Global</string>
|
<string name="follow_list_global">Global</string>
|
||||||
|
|
||||||
|
<string name="app_notification_channel_id" translatable="false">DefaultChannelID</string>
|
||||||
|
<string name="app_notification_private_message" translatable="false">New notification arrived</string>
|
||||||
|
|
||||||
|
<string name="app_notification_dms_channel_id" translatable="false">PrivateMessagesID</string>
|
||||||
|
<string name="app_notification_dms_channel_name">Private Messages</string>
|
||||||
|
<string name="app_notification_dms_channel_description">Notifies you when a private message arrives</string>
|
||||||
|
|
||||||
|
<string name="app_notification_zaps_channel_id" translatable="false">ZapsID</string>
|
||||||
|
<string name="app_notification_zaps_channel_name">Zaps Received</string>
|
||||||
|
<string name="app_notification_zaps_channel_description">Notifies you when somebody zaps you</string>
|
||||||
|
<string name="app_notification_zaps_channel_message">%1$s sats</string>
|
||||||
|
<string name="app_notification_zaps_channel_message_from">from %1$s</string>
|
||||||
|
<string name="app_notification_zaps_channel_message_for">for %1$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
29
app/src/play/AndroidManifest.xml
Normal file
29
app/src/play/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".Amethyst">
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.notifications.PushNotificationReceiverService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<meta-data android:name="com.google.firebase.messaging.default_notification_icon"
|
||||||
|
android:resource="@drawable/amethyst" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_color"
|
||||||
|
android:resource="@color/purple_500" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
|
android:value="@string/app_notification_channel_id" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@@ -0,0 +1,22 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingService
|
||||||
|
import com.google.firebase.messaging.RemoteMessage
|
||||||
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
|
|
||||||
|
class PushNotificationReceiverService : FirebaseMessagingService() {
|
||||||
|
|
||||||
|
// this is called when a message is received
|
||||||
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||||
|
remoteMessage.data.let {
|
||||||
|
val eventStr = remoteMessage.data["event"] ?: return
|
||||||
|
val event = Event.fromJson(eventStr, true)
|
||||||
|
EventNotificationConsumer(applicationContext).consume(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewToken(token: String) {
|
||||||
|
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.notifications
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.android.gms.tasks.OnCompleteListener
|
||||||
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class PushNotificationUtils {
|
||||||
|
fun init(accounts: List<AccountInfo>) {
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
scope.launch {
|
||||||
|
// get user notification token provided by firebase
|
||||||
|
FirebaseMessaging.getInstance().token.addOnCompleteListener(
|
||||||
|
OnCompleteListener { task ->
|
||||||
|
if (!task.isSuccessful) {
|
||||||
|
Log.w("FirebaseMsgService", "Fetching FCM registration token failed", task.exception)
|
||||||
|
return@OnCompleteListener
|
||||||
|
}
|
||||||
|
// Get new FCM registration token
|
||||||
|
val notificationToken = task.result
|
||||||
|
|
||||||
|
RegisterAccounts(accounts).go(notificationToken)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -8,6 +8,9 @@ buildscript {
|
|||||||
accompanist_version = '0.30.0'
|
accompanist_version = '0.30.0'
|
||||||
coil_version = '2.3.0'
|
coil_version = '2.3.0'
|
||||||
}
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.google.gms:google-services:4.3.15'
|
||||||
|
}
|
||||||
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.0.1' apply false
|
id 'com.android.application' version '8.0.1' apply false
|
||||||
|
Reference in New Issue
Block a user